MogLog

メモというか日記というか備忘録というか

Rubyの`&&`と`||`

&&

&& をよく使うのは、if 式などでの条件を書くときだろう。つまるところ真か偽の結果を得ることを期待していると思う。

cond1 = true
cond2 = false
cond3 = true

if cond1 && cond2
  # 実行されない
end

if cond1 && cond3
  # 実行される
end

ここで気をつけたいのは返り値は true または false の2択では無いということ。 演算子式 (Ruby 2.6.0) には次のように書かれている。

左辺を評価し、結果が偽であった場合はその値(つまり nil か false) を返します。左辺の評価結果が真であった場合には 右辺を評価しその結果を返します。

つまり左辺が偽であればそれが返るので nil または false があり得るし、左辺が真の場合は nilfalseに加えて様々な真の値が返り得るということ。

pp false && true # => false
pp nil && true # => nil

pp true && true # => true
pp true && false # => false
pp true && nil # => nil
pp true && 'hello' # => "hello"
pp true && 100 # => 100
pp true && [] # => []

Rubyでは nilfalse は偽でそれ以外は真という性質があるので、条件式として書く場合に問題になることは少ないと思うがこの動作はちゃんと認識しておこう。

||

|| も似たような感じ。演算子式 (Ruby 2.6.0)に次のように書かれている。

左辺を評価し、結果が真であった場合にはその値を返します。 左辺の評価結果が偽であった場合には右辺を評価し その評価結果を返します。

pp true || false # => true
pp true || 'hello' # => true
pp 'hello' || true # => "hello"

pp false || true # => true
pp false || 'hello' # => "hello"
pp false || [] # => []
pp false || 100 # => 100

&&|| より評価の優先順位が高い

演算子式 (Ruby 2.6.0) に書かれているように、優先順位としては && の方が || より高い。

つまり次のような式があった場合は..

a && b || c && d

次のように脳内変換しよう。

(a && b) || (c && d)

おまけ:andor

and&& と、or|| とそれぞれ同じだが、優先順位が異なる。
この4つだと && > || > and, or という順序になる。

自分はあまり使わないが、この記事を書きながら調べていて以下の記事は参考になった。 techracho.bpsinc.jp

後置の ifunless を使って書くことが多いけど、andor をうまく使うとより読みやすくなる場面も多そうだな〜と思った。

# dataの取得に失敗したら例外を投げる
fetch_data or raise RuntimeError "failed to fetch data"

# 更新があるかどうかをチェックし、なければreturnで終了
updates = check_for_update or return

mysqldumpに`--complete-insert`オプションをつけるとカラム名を含むINSERT文を出力してくれる

mysqldumpコマンドのデフォルト状態で出力されるINSERT文にはカラム名の情報が含まれていない。以下例(※ 見やすいようにインデントしている)。

INSERT INTO 
  `users` 
VALUES 
  (1,'taro',20,'male',1,'2019-02-13 23:44:02','2019-02-13 23:44:02'),
  (2,'jiro',30,'male',1,'2019-02-13 23:44:11','2019-02-13 23:44:11')
;

dump元とrestore先で全く同じテーブル構成であれば特に問題はないが、例えばカラム名の順番がちょっと違っているような場合だとデータがズレた状態でINSERTされることになってしまう。

そのようなときは、--complete-insert or -c オプションをつけてmysqldumpを実行するとカラム名を含んだINSERT文を出力してくれるようになる。

INSERT INTO 
  `users` (`id`, `name`, `age`, `sex`, `premium`, `created_at`, `updated_at`) 
VALUES 
  (1,'taro',20,'male',1,'2019-02-13 23:44:02','2019-02-13 23:44:02'),
  (2,'jiro',30,'male',1,'2019-02-13 23:44:11','2019-02-13 23:44:11')
;

ちなみにmysqldumpのmanを見るとそのままのことが書かれている

       o   --complete-insert, -c

           Use complete INSERT statements that include column names.

RSpec with Railsでテスト時のデータはどのように削除されているか

RailsアプリでRSpecを使うと通常example実行のたびにデータが削除されるようになっているが、これが具体的にどのように行われているかについて。

検証環境は以下の通り。

use_transactional_fixtures

RailsRspecを使うとき、bundle exec rails generate rspec:installを実行して初期設定をすることになると思うが、そのときに spec/rails_helper.rb という設定ファイルも同時に作られる。

このファイルにデフォルトで次のような設定が記載されている。

RSpec.configure do |config|
  # ..省略

  config.use_transactional_fixtures = true

  # ..省略
end

この use_transactional_fixtures という設定項目が true になっていると、exampleのたびにデータが削除されるようになる。

削除の仕組み

use_transactional_fixtures という設定名からは想像がつきにくいのだが、1つのexampleがデータベースのトランザクションの中で実行されるようになり、exampleの終わりにロールバックされ、データが消えるという仕組みになっている。

テストを実行したときの log/test.log を見るとその様子がよくわかる。

(0.1ms)  begin transaction
(0.1ms)  SAVEPOINT active_record_1

// ここでexampleが実行。テスト内容に応じた各種SQLが出力される

(0.1ms)  RELEASE SAVEPOINT active_record_1
(0.5ms)  rollback transaction

exampleの実行前に begin transaction、終わりに rollback transaction というログが出力されている。

before(:each)before(:all)

これは本題とは関係無いが、関連知識として知っておいた方が良いと思ったのでついでに書く。

  • before(:each) ブロックに書いた事前処理は先のrollbackの対象となり、明示的に削除処理を書かなくても自動で削除される。
  • 一方、before(:all) ブロックに書いた事前処理はtransactionの外側で実行されるため自動では削除されない。必要に応じて明示的に事後処理を書く必要がある。

Tips

use_transactional_fixturesエイリアスとして、use_transactional_examples も使えるようになっている。
https://github.com/rspec/rspec-rails/blob/5a9182b24c7370ba293fcdd13d7ce01e6d24cd2e/lib/rspec/rails/configuration.rb#L65

こちらの方が名前としてわかりやすいね。

参考リンク

書き出して自分と対話・思考しながら仕事を進めるスタイルとそれについて考えたこと

ふと考えたことをとりとめもなく書いてみる。

書きながら自分と対話・思考しながら物事を進めるスタイル

自分は仕事をするときに、作業の記録を書き残しながら進めることが多い。 書いているのは以下のような内容。

  • 何のためにやるのか
  • どうしてやることになったのか
  • どうやって実現するか
  • 実際にどんな作業をしたか

今書いて気づいたけど、目的、背景、実現手段、作業記録という構成になることが多い。

こうしたことを書きながら、自分と対話するようにひとつひとつを納得・理解して順番に進める。

もし自分と対話しながら考えた結果、わからないことがあれば知っていそうな人に話を聞く。話した結果、得られたものをまた書き残す。

たまに、聞いてもよくわからなかったけどとりあえずやる必要があるということだけがわかるときもある(そういうときはモチベーションが下がる)。

どうしてこのスタイルになったのか

よくそんなに面倒なことをやるなと言われることも多いが、単にこのスタイルじゃないとうまく作業を進めることができなくなってしまっただけなのである。

なぜこのスタイルになったのか、というのをふと考えたところ、おそらく自分は頭の中だけで考える力が弱いんじゃないか、という気がした。あるいはこのスタイルでやり続けた結果、考える力が衰えたのかもしれない。

その根拠として、知り合いの中に仕事終わりに家に帰りながら考えていたら解決策がひらめくという人が何人かいるが、自分はそういう体験はほとんど無い。そもそも帰りながら深く考えるということをあまりしていない気がするし、考えても脳内のメモリがすぐいっぱいになってそれ以上思考を進めることが難しくなる。

また少し込み入った内容の話・説明になると、その内容をその場で理解することは大体できない(周りの人を見ていると、結構理解できているように見えるのだが)。なので、そういうときは一旦席に戻って、話していた内容のうち覚えていることをバーっと書き出して、そこからまた自分と対話しながら状況を整理する、というようなことをやっていた。

対話がキーポイント

ただ最近、書き出さなくてもうまくできているというかスムーズに進められていると感じることがあった。 それはおそらく一緒に働いている人のおかげで、これまで書きながら自分と対話していたことを、その人と実際に対話しながら考えることができるようになったからなんじゃないかと感じている。

すごい初歩的な部分とか、そもそもの前提部分とか、本題とは関係無さそうなちょっとした疑問とか、他人に聞くのは少し憚られると思う。でもそういったことを気軽に会話できる。そうするとスムーズに物事が進められる。

これが心理的安全性か!と思った。 Googleの研究で成果の高いチームは心理的安全性が確保されている、というような話を聞いたことがあるが、それを実際に感じられた体験。気軽な対話の重要性を感じた。

※ 記事あった gendai.ismedia.jp

反対に、「そこから(説明が必要ですか)?」、「だから〜(に決まってるでしょ)」、「もう言いましたよね(だからもう聞くな)」といったニュアンスのセリフが出てくる会話環境は厳しい。

チームで仕事をする上では、専門的なスキルよりも大切な能力だと感じざるを得ない。少なくとも自分ならそういう人と働きたいと思う。

まとめ

とりとめもなく書いたことだがまとめてみよう

  • 自分の仕事の進め方スタイルを思い返してみたところ、頭の中だけで考えることが苦手なのではないかと気がついた
  • その対策として、知らず知らずのうちに書きながら自分と対話するというスタイルになっていた(?)
  • 対話が重要だった

おしまい

「既存のコードがそうなっているから」は逃げる理由になりがち

何らかの追加実装の作業をしているとき、往々にして既存のコードと類似した実装をする、あるいはそうできる場面に遭遇することがあると思う。

このような時、既存のコードと同じパターン・同じ構成で実装するというのは1つの選択肢になる。 なぜならそのように書いたコードは、現在のコードとの調和を保ちつつも「既に動いている」という実績を持ち合わせているからだ。 多くを考えること無く、短時間で動くコードを実装できることも開発者には嬉しい点だ。

一方このやり方は、考えることから逃げる理由になりがちであるとも感じる。 本来であれば要件に応じた最適なコードを自分の中で考えるフェーズがあるはずだが、既存実装があるとこのフェーズをすっ飛ばしてしまうことができる。

既に動いていたとしても、既存のコードが適切な実装であるとは限らない。仮に適切であったとしても、今から追加をする段階で適切ではなくなる可能性も高い。

既存のコードを踏襲するにあたっては、「自分だったらこの要件をどう実現するか」をゼロベースで考えることや、「本当にこの既存実装は適切か」、「より良い実装は無いだろうか」というような点をまずは考えることを意識したい。

体験としては既存の実装は少なからず改善点を抱えているので、調和なんて知ったことかくらいの勢いで、自分が考える最適実装に仕上げるのがいいんじゃないかと感じている。

【Rails】データベース名を明示せずにActiveRecordでMySQLに接続する

環境

状況

データベースはまだ存在しないが、ActiveRecordMySQLへの接続だけは確立したい。

何も考えずに次のようなコードを実行すると...

ApplicationRecord.connection.execute("<なんかSQL>")

次のようなエラーが起こる。

ActiveRecord::NoDatabaseError: Unknown database '<データベース名>'

どうするか

このような場合は、次のようにすると比較的スッキリ書ける。

config = ApplicationRecord.connection_config
ApplicationRecord.establish_connection(config.merge(database: nil)) # ★
ApplicationRecord.connection.execute("<なんかSQL>")

こんなんいつ役に立つんじゃい

自分は以下のような状況で使った。

  • 一時的に複数のデータベースを扱う必要があるRailsアプリケーションがある(そのためdatabase.ymlが2つある状況)
  • 2つ目のデータベースの作成と削除も、rakeタスクでRailsらしく実行したい
    • bin/rails 2nd_db:createとかそんな感じで
  • 2つ目のデータベース用の基底クラス(ActiveRecord::Baseを継承したクラス)があるので、この基底クラスから CREATE DATABASE <2つ目のデータベース> を発行したい。だが単純に実行しようとすると先述の通りエラーになる。
  • 実装の都合上、1つ目のデータベースに接続するApplicationRecordはできれば使いたくない
  • そこでコレ

状況がニッチすぎて誰得な感じは否めないが、自分はそれなりに悩んだのでインターネットの海に放流しておこうと思った次第である。その他、エレガントな実装方法があれば教えてほしい。

【Rails】app/以下に新規作成したディレクトリのファイルが読み込まれない件

結論:springの再起動が必要だった

あらまし

Ruby On Railsアプリケーションの開発中、app/以下に新しくvalidatorsというディレクトリを作ることになった。 最近のRailsではapp以下のファイルを自動で読み込んでくれるようになっているので、追加の設定は不要だ。

Railsガイドにも次のように書かれている。

ところで、Railsにはpost.rbのようなファイルを探索する$LOAD_PATHに似た、ディレクトリのコレクションがあります。このコレクションはautoload_pathsと呼ばれており、デフォルトで以下が含まれます。

アプリケーションとエンジンのappディレクトリ以下にあるすべてのサブディレクトリ。app/controllersなどが対象。app以下に置かれるapp/workersなどのカスタムディレクトリはすべてautoload_pathsに自動的に属するので、デフォルトのディレクトリとする必要はない。

定数の自動読み込みと再読み込み | Rails ガイド

そう、そのはずなのだが何故か読み込まれない。 bin/rails cでコンソールを立ち上げて、$LOAD_PATHActiveSupport::Dependencies.autoload_pathsを確認してもやはりapp/validatorsは含まれていない。

1時間くらいハマッて調べた結果、springがキャッシュを持っていることが原因だとわかった。

$ bin/spring stop

上のコマンドでspringを停止し、その後 bin/rails c でコンソールを立ち上げて確認したところ無事に読み込まれていた。 springは裏でひっそりと動くのであまり気にしなくて良い反面、存在自体を忘れがちである。