2014-01-06 10 views
25

スコープを作成してfirstファインダを使用するという奇妙な問題が発生しています。スコープ内のクエリの一部としてfirstを使用すると、結果が見つからない場合はすべての結果が返されるように見えます。結果が見つかると、最初の結果が正しく返されます。このテストのためのRailsスコープはnilの代わりにすべてを返します

class Activity::MediaGroup < ActiveRecord::Base 
    scope :test_fail, -> { where('1 = 0').first } 
    scope :test_pass, -> { where('1 = 1').first } 
end 

注意、私が設定した条件は、レコードかどうかにマッチする:

私はセットアップにこれを実証するための非常に簡単なテストを持っています。実際には、私は実際の条件に基づいてクエリを実行していて、同じ不思議な振る舞いをしています。

ここに失敗したスコープの結果があります。あなたが見ることができるように、それは結果を持っていない、正しいクエリを行い、その代わりに、すべての一致するレコードを返すために、それ、その後のクエリ:

irb(main):001:0> Activity::MediaGroup.test_fail 
    Activity::MediaGroup Load (0.0ms) SELECT "activity_media_groups".* FROM "activity_media_groups" WHERE (1 = 0) ORDER BY "activity_media_groups"."id" ASC LIMIT 1 
    Activity::MediaGroup Load (0.0ms) SELECT "activity_media_groups".* FROM "activity_media_groups" 
=> #<ActiveRecord::Relation [#<Activity::MediaGroup id: 1, created_at: "2014-01-06 01:00:06", updated_at: "2014-01-06 01:00:06", user_id: 1>, #<Activity::MediaGroup id: 2, created_at: "2014-01-06 01:11:06", updated_at: "2014-01-06 01:11:06", user_id: 1>, #<Activity::MediaGroup id: 3, created_at: "2014-01-06 01:26:41", updated_at: "2014-01-06 01:26:41", user_id: 1>, #<Activity::MediaGroup id: 4, created_at: "2014-01-06 01:28:58", updated_at: "2014-01-06 01:28:58", user_id: 1>]> 

他の範囲には、期待通りに動作します。

irb(main):002:0> Activity::MediaGroup.test_pass 
    Activity::MediaGroup Load (1.0ms) SELECT "activity_media_groups".* FROM "activity_media_groups" WHERE (1 = 1) ORDER BY "activity_media_groups"."id" ASC LIMIT 1 
=> #<Activity::MediaGroup id: 1, created_at: "2014-01-06 01:00:06", updated_at: "2014-01-06 01:00:06", user_id: 1> 

スコープ外で同じロジックを実行すると、予期した結果が得られます。

irb(main):003:0> Activity::MediaGroup.where('1=0').first 
    Activity::MediaGroup Load (0.0ms) SELECT "activity_media_groups".* FROM "activity_media_groups" WHERE (1=0) ORDER BY "activity_media_groups"."id" ASC LIMIT 1 
=> nil 

ここに何か不足していますか?これはRails/ActiveRecord/Scopesのバグのように思えますが、私が気づいていない未知の行動の期待がない限り、私には分かりません。

+0

'右、記録、ないARELを返す.first'? – Satya

+0

あなたはどのバージョンのルビーとレールを使用していますか? – shiva

+0

@shiva - Rails 4とRuby 2.0 – Ryan

答えて

48

これはバグや奇妙なことではなく、の目的のために設計されたいくつかの調査の結果、です。

まず、

  1. scope戻りActiveRecord::Relation

  2. ゼロがある場合は、再びActiveRecord::Relation代わりのnil

あるすべてのレコード を返すようにそのプログラムさを記録します

これの背後にある考え方は、のスコープチェーン可能(IE)scopeclass methods重要な違いの1

例:最近のことで、ユーザーは状況によって記事をフィルタリングすることができるようになります、順序付け:

次のシナリオを使用できるようにします更新されたもの。

class Post < ActiveRecord::Base 
    scope :by_status, -> status { where(status: status) } 
    scope :recent, -> { order("posts.updated_at DESC") } 
end 

そして、我々は次のように自由にそれらを呼び出すことができます:十分なシンプルな、そのための書き込みスコープをすることができますので、

Post.by_status(params[:status]).recent 
# SELECT "posts".* FROM "posts" WHERE "posts"."status" = 'published' 
# ORDER BY posts.updated_at DESC 

これまでのところ、:

Post.by_status('published').recent 
# SELECT "posts".* FROM "posts" WHERE "posts"."status" = 'published' 
# ORDER BY posts.updated_at DESC 

あるいは、ユーザ提供のparamと

良い。比較のためにクラスメソッドに移動してみましょう:

class Post < ActiveRecord::Base 
    def self.by_status(status) 
    where(status: status) 
    end 

    def self.recent 
    order("posts.updated_at DESC") 
    end 
end 

さらにいくつかの行を追加するだけでなく、大きな改善はありません。しかし、:statusパラメータがnilまたは空白の場合はどうなりますか?

Post.by_status(nil).recent 
# SELECT "posts".* FROM "posts" WHERE "posts"."status" IS NULL 
# ORDER BY posts.updated_at DESC 

Post.by_status('').recent 
# SELECT "posts".* FROM "posts" WHERE "posts"."status" = '' 
# ORDER BY posts.updated_at DESC 

私たちはこれらのクエリを許可したかったと思いませんか?スコープでは、我々は簡単に私達のスコープに存在条件を追加することによって、これを修正することができます

scope :by_status, -> status { where(status: status) if status.present? } 

があり私達は行く:

Post.by_status(nil).recent 
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC 

Post.by_status('').recent 
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC 

恐ろしいです。今、私たちの最愛のクラスメソッドと同じことを試すことができます:

Post.by_status('').recent 
NoMethodError: undefined method `recent' for nil:NilClass 

そして:この実行

class Post < ActiveRecord::Base 
    def self.by_status(status) 
    where(status: status) if status.present? 
    end 
end 

爆弾を:.相違点は、スコープは常にリレーションを返しますが、単純なクラスメソッドの実装ではリレーションを返さないことです。私はRailsの4に(それが以前にデータベースからアイテムの配列を返した)関係を返す空白/ゼロの場合、のすべてを返すよ

def self.by_status(status) 
    if status.present? 
    where(status: status) 
    else 
    all 
    end 
end 

お知らせ:クラスメソッドではなく、次のようになります。 Rails 3.2.xでは、代わりにスコープを使用する必要があります。そして、そこに私達は行く:

Post.by_status('').recent 
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC 

だからここにアドバイスがある:常に関係を返すことを、そうでなければ、スコープによって暗黙chainability条件を壊している、スコープのように動作するはずクラスメソッドからnilを返すことはありません。

かいつまん:どんなにスコープは、それが連鎖可能にするためにActiveRecord::Relationを返すように意図されているものを、

。あなたがfirstlastまたはfind結果を期待している場合は、class methods

ソースを使用する必要があります。http://blog.plataformatec.com.br/2013/02/active-record-scopes-vs-class-methods/

+0

制限を使用たとえ1の制限があっても、Active Record Relationを返しますが、最初にモデルのインスタンスを返します。したがって、機能は異なり、私の場合は重要です。あるモデルが存在する場合、そのモデルをスコープから取得する方法はありますか?リレーションではありませんか? – Ryan

+0

@Ryanはクラスメソッドを使用しない理由は何ですか? – shiva

+1

情報をありがとう、私はあなたが言っていることを見る...と私は推論を参照してください。スコープが明示的に真ではないものを返すべきであるということについて私が完全に同意しているとはまだ思っていませんが。アクティブレコード関係にゼロレコードが含まれていない可能性がありますか?最初、最後、または検索などの関数を使用する場合は、スコープを使用しないことを非常に意識していなければなりません。 – Ryan

関連する問題