空のActiveRecordリレーションを返す方法は?


246

ラムダ付きのスコープがあり、引数を取る場合、引数の値によっては、一致がないことはわかっているかもしれませんが、空の配列ではなく、関係を返したいと思います。

scope :for_users, lambda { |users| users.any? ? where("user_id IN (?)", users.map(&:id).join(',')) : [] }

私が本当に望んでいるのは、「すべて」の反対である「なし」メソッドです。これは、まだ連鎖できるリレーションを返しますが、クエリが短絡されます。


クエリをそのままにしておくと、実行するとリレーションが返されます。User.where( 'id in(?)'、[])。class => ActiveRecord :: Relation。クエリを完全に回避しようとしていますか?
Brian Deterling

1
正しい。一致する可能性がないことがわかっている場合は、理想的にはクエリを完全に回避できます。私はこれを単にActiveRecord :: Base: "def self.none; where(:id => 0); end"に追加しました。
dzajic

1
>クエリを完全に回避しようとしていますか?完全に理にかなっていて、そのためにDBをヒットする必要がある
なんて足り

回答:


478

Rails 4に「正しい」メカニズムが追加されました。

>> Model.none 
=> #<ActiveRecord::Relation []>

14
これまでのところ、これは3.2以前に移植されていません。オンリーエッジ(4.0)
クリスブルーム


2
Rails 4.0.5で試したところ、問題なく動作しました。この機能により、Rails 4.0リリースに到達しました。
2014

3
@AugustinRiedingerはModel.scopedあなたがレール3に探しているものありません
ティムDiggins

9
Rails 4.0.5以降で Model.noneは機能しません。実際のモデル名の1つを使用する必要があります(例:User.nonewhat-have-you)。
Grant Birchmeier、2015年

77

「id」列を必要とせず、IDが0の行がないことを前提としない、より移植性の高いソリューション:

scope :none, where("1 = 0")

私はまだもっと「正しい」方法を探しています。


3
ええ、私はこれらの答えが私たちが持っている中で最高であることに本当に驚いています。ActiveRecord / Arelはまだ未熟だと思います。Rubyで空の配列を作成するために散歩を行わなければならない場合、私は本当にイライラするでしょう。ここでも基本的には同じです。
パープルジャケット

多少ハックですが、これ Rails 3.2の正しい方法です。Rails 4については、@ steveh7の他の回答をここで参照してください:stackoverflow.com/a/10001043/307308
scarver2

scope :none, -> { where("false") }
1

43

Rails 4で登場

Rails 4では、次のActiveRecord::NullRelationような呼び出しからchainable が返されますPost.none

それもチェーンメソッドもデータベースへのクエリを生成しません。

コメントによると:

返されたActiveRecord :: NullRelationはRelationを継承し、Nullオブジェクトパターンを実装します。これはnullの動作が定義されたオブジェクトであり、データベースを照会せずに常にレコードの空の配列を返します。

ソースコードを参照してください。


42

「none」というスコープを追加できます。

scope :none, where(:id => nil).where("id IS NOT ?", nil)

空のActiveRecord :: Relationが表示されます

イニシャライザのActiveRecord :: Baseに追加することもできます(必要な場合)。

class ActiveRecord::Base
 def self.none
   where(arel_table[:id].eq(nil).and(arel_table[:id].not_eq(nil)))
 end
end

このようなものを取得する方法はたくさんありますが、コードベースを維持するのに最善の方法ではありません。空のActiveRecord :: Relationを短時間保証する必要があることをリファクタリングしているときに、スコープ:noneを使用しました。


14
where('1=2')もう少し簡潔かもしれません
Marcin Raczkowski

10
新しい「正しい」答えまで下にスクロールしない場合Model.noneは、次のようにします。
Joe Essey、

26
scope :none, limit(0)

スコープが連鎖する可能性があるため、危険な解決策です。

User.none.first

最初のユーザーを返します。使用する方が安全です

scope :none, where('1 = 0')

2
これは正しいものです 'scope:none、where(' 1 = 0 ')'。ページネーションがある場合、もう1つは失敗します
フェデリコ

14

私はこれが他のオプションよりも見える方法を好むと思います:

scope :none, limit(0)

このようなものにつながる:

scope :users, lambda { |ids| ids.present? ? where("user_id IN (?)", ids) : limit(0) }

私はこれを好む。なぜwhere(false)仕事をしないのかはわかりませんが、スコープは変更されません。
aceofspades

4
Railsがそのクエリに追加するため、limit(0)呼び出す.first.lastチェーンの後半でオーバーライドされることに注意してくださいLIMIT 1
zykadelic

2
@aceofspades where(false)は機能しません(Rails 3.0)が、where( 'false')は機能します。2013年だと気になっているわけではないでしょう:)
Ritchie

@Ritchieに感謝します。それ以来、none以下のような関係もあると思います。
aceofspades

3

スコープを使用:

scope:for_users、lambda {| users | users.any??where( "user_id IN(?)"、users.map(&:id).join( '、')):スコープ}

ただし、次の方法でコードを簡略化することもできます。

scope:for_users、lambda {| users | where(:user_id => users.map(&:id))if users.any?}

空の結果が必要な場合は、これを使用します(if条件を削除します)。

scope:for_users、lambda {| users | where(:user_id => users.map(&:id))}

"scoped"またはnilを返すと、結果がゼロに制限されるという目的が達成されません。"scoped"またはnilを返すことは、スコープに影響を与えません(これは、場合によっては便利ですが、私の場合には役立ちません)。私は自分の答えを思いつきました(上記のコメントを参照)。
dzajic

空の結果も返す簡単なソリューションを追加しました:)
Pan Thomakos


1

バリアントもありますが、これらはすべてdbに要求を出しています

where('false')
where('null')

2
ところでこれらは文字列でなければならないことに注意してください。where(false)またはwhere(nil)単に無視されます。
mahemoff
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.