Rails4でscope内で first などを使用するとデータの有無により挙動が変わるので注意が必要

Rails4でscopeを定義するときに、scope内で first や maximum などの集計関数を使用するとデータの存在有無により発行されるSQLが異なるようです。(本家のドキュメントなどは確認できていないので挙動としてそのようになっている程度ですが…)

環境

利用している環境は以下の通りです。

定義しているscope

[ruby] scope :valid_data, ->(target_year_month){ target_date = Date.new(target_year_month.slice(0,4).to_i, target_year_month.slice(4,2).to_i, 1) where('valid_start_date <= ?', target_date).where('valid_end_date >= ?', target_date).first } [/ruby]

データが存在する場合に発行されるSQL

[bash] >> GensenChoshuZeiRitsu.valid_data('201411') GensenChoshuZeiRitsu Load (0.7ms) SELECT "gensen_choshu_zei_ritsus".* FROM "gensen_choshu_zei_ritsus" WHERE (valid_start_date <= '2014-11-01') AND (valid_end_date >= '2014-11-01') ORDER BY "gensen_choshu_zei_ritsus"."id" ASC LIMIT 1

<GensenChoshuZeiRitsu id: 2...>

>> GensenChoshuZeiRitsu.valid_data('201411').blank? GensenChoshuZeiRitsu Load (0.7ms) SELECT "gensen_choshu_zei_ritsus".* FROM "gensen_choshu_zei_ritsus" WHERE (valid_start_date <= '2014-11-01') AND (valid_end_date >= '2014-11-01') ORDER BY "gensen_choshu_zei_ritsus"."id" ASC LIMIT 1

=> false

[/bash]

データが存在しない場合に発行されるSQL

[bash] >> GensenChoshuZeiRitsu.valid_data('200011') GensenChoshuZeiRitsu Load (1.9ms) SELECT "gensen_choshu_zei_ritsus". FROM "gensen_choshu_zei_ritsus" WHERE (valid_start_date <= '2000-11-01') AND (valid_end_date >= '2000-11-01') ORDER BY "gensen_choshu_zei_ritsus"."id" ASC LIMIT 1 GensenChoshuZeiRitsu Load (0.3ms) SELECT "gensen_choshu_zei_ritsus". FROM "gensen_choshu_zei_ritsus"

<ActiveRecord::Relation [#<GensenChoshuZeiRitsu id: 1, ...]>

>> GensenChoshuZeiRitsu.valid_data('200011').blank? GensenChoshuZeiRitsu Load (0.5ms) SELECT "gensen_choshu_zei_ritsus".* FROM "gensen_choshu_zei_ritsus" WHERE (valid_start_date <= '2000-11-01') AND (valid_end_date >= '2000-11-01') ORDER BY "gensen_choshu_zei_ritsus"."id" ASC LIMIT 1

どうして全件データを取得する???

GensenChoshuZeiRitsu Load (0.4ms) SELECT "gensen_choshu_zei_ritsus".* FROM "gensen_choshu_zei_ritsus"

ここがfalseになるのがおかしい!!!

=> false

[/bash]

結論

挙動を見る限り、データが存在しない場合に raise_record_not_found_exception が発生するのではなく無理に無条件のデータロードを実行しているよです。どうやら scope からは ActiveRecord::Relation を返却するように考慮しないと思わぬバグを作り込むように思います。