結論から先に。特定のケースでscope内でnilとなる場合は、allになる。
first指定してても条件により全件取れるので、後続の処理(1件を期待した処理)でコケる。
常になる訳ではなく、データに依存するので気付き難くて厄介。

理由は、scopeの目的から外れるからで、クラスメソッドを使うべきとの事。
ケース毎に使い分けても良いが、複数人で開発する場合、クラスメソッドに統一するのもありかも。
ActiveRecordのScopeは、連鎖できない使い方はしてはいけない(first等)

NGな例

app/models/user.rb

  scope :scope_test1, ->(created_at) do
    where('created_at > ?', created_at).order(sign_in_count: :desc).first
  end
% rails c
3.0.0 :001 > User.scope_test1(Time.current)
   (12.7ms)  SET NAMES utf8 COLLATE utf8_bin,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
  User Load (2.0ms)  SELECT `users`.* FROM `users` WHERE (created_at > '2021-05-30 01:30:09.794713') ORDER BY `users`.`sign_in_count` DESC LIMIT 1
  User Load (0.2ms)  SELECT `users`.* FROM `users` LIMIT 11

3.0.0 :002 > User.scope_test1(Time.current).count
  User Load (0.4ms)  SELECT `users`.* FROM `users` WHERE (created_at > '2021-05-30 01:30:26.029438') ORDER BY `users`.`sign_in_count` DESC LIMIT 1
   (2.9ms)  SELECT COUNT(*) FROM `users`
 => 5

0件を期待したが、勝手に「SELECT `users`.* FROM `users` LIMIT 11」されてしまう。

OKな例

app/models/user.rb

  scope :scope_test2, ->(created_at) do
    where('created_at > ?', created_at).order(sign_in_count: :desc)
  end
% rails c
3.0.0 :001 > User.scope_test2(Time.current)
   (0.4ms)  SET NAMES utf8 COLLATE utf8_bin,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
  User Load (0.3ms)  SELECT `users`.* FROM `users` WHERE (created_at > '2021-05-30 01:33:02.057989') ORDER BY `users`.`sign_in_count` DESC LIMIT 11
 => # 

3.0.0 :002 > User.scope_test2(Time.current).count
   (0.4ms)  SELECT COUNT(*) FROM `users` WHERE (created_at > '2021-05-30 01:33:56.444797')
 => 0 

firstがなければ、期待通り0件になる。

クラスメソッドに変更

app/models/user.rb

  class << self
    def scope_test1(created_at)
      where('created_at > ?', created_at).order(sign_in_count: :desc).first
    end

    def scope_test2(created_at)
      where('created_at > ?', created_at).order(sign_in_count: :desc)
    end
  end
% rails c
3.0.0 :001 > User.scope_test1(Time.current)
   (0.4ms)  SET NAMES utf8 COLLATE utf8_bin,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
  User Load (0.9ms)  SELECT `users`.* FROM `users` WHERE (created_at > '2021-05-30 01:41:34.636951') ORDER BY `users`.`sign_in_count` DESC LIMIT 1
 => nil 

期待通りになりました。

3.0.0 :002 > User.scope_test2(Time.current).count
   (0.4ms)  SELECT COUNT(*) FROM `users` WHERE (created_at > '2021-05-30 01:43:27.642558')
 => 0 

こちらも問題なし。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です