web-dev-qa-db-ja.com

ActiveRecord Arel OR条件

ANDの代わりに論理ORを使用して2つの異なる条件を結合するにはどうすればよいですか?

注:2つの条件はRailsスコープとして生成され、where("x or y") 直接。

簡単な例:

admins = User.where(:kind => :admin)
authors = User.where(:kind => :author)

AND条件を適用するのは簡単です(この特定のケースでは意味がありません):

(admins.merge authors).to_sql
#=> select ... from ... where kind = 'admin' AND kind = 'author'

しかし、2つの異なるArelリレーションがすでに利用可能な次のクエリをどのように生成できますか?

#=> select ... from ... where kind = 'admin' OR kind = 'author'

Arel readmeによる ):

OR演算子はまだサポートされていません

しかし、ここでは当てはまらず、次のような記述を期待しています。

(admins.or authors).to_sql
56

私はパーティーに少し遅れましたが、ここに私が思いつくことができる最高の提案があります:

admins = User.where(:kind => :admin)
authors = User.where(:kind => :author)

admins = admins.where_values.reduce(:and)
authors = authors.where_values.reduce(:and)

User.where(admins.or(authors)).to_sql
# => "SELECT \"users\".* FROM \"users\"  WHERE ((\"users\".\"kind\" = 'admin' OR \"users\".\"kind\" = 'author'))"
70
jswanner

ActiveRecordクエリは、ActiveRecord::Relationオブジェクト(orをサポートしていません)であり、Arelオブジェクト(サポートしている)ではありません。

[[〜#〜] update [〜#〜]:Rails 5、 "or"がサポートされているin ActiveRecord::Relation;参照 https://stackoverflow.com/a/33248299/190135 ]

しかし幸いなことに、whereメソッドはARelクエリオブジェクトを受け入れます。したがって、User < ActiveRecord::Base...

users = User.arel_table
query = User.where(users[:kind].eq('admin').or(users[:kind].eq('author')))

query.to_sqlに安心感が表示されます:

SELECT "users".* FROM "users"  WHERE (("users"."kind" = 'admin' OR "users"."kind" = 'author'))

明確にするために、いくつかの一時的な部分クエリ変数を抽出できます。

users = User.arel_table
admin = users[:kind].eq('admin')
author = users[:kind].eq('author')
query = User.where(admin.or(author))

そして当然、クエリを取得したら、query.allを使用して実際のデータベース呼び出しを実行できます。

91
AlexChaffee

実際のアレルページ から:

OR演算子は次のように機能します。

users.where(users[:name].eq('bob').or(users[:age].lt(25)))
9
Dave Newton

Rails 5現在、ActiveRecord::Relation#or、これを行うことができます:

User.where(kind: :author).or(User.where(kind: :admin))

...あなたが期待するSQLに翻訳されます:

>> puts User.where(kind: :author).or(User.where(kind: :admin)).to_sql
SELECT "users".* FROM "users" WHERE ("users"."kind" = 'author' OR "users"."kind" = 'admin')
8
pje

Mongoidの#any_ofに代わるactiverecordを探しているのと同じ問題に遭遇しました。

@jswannerの答えは良いですが、whereパラメータがハッシュの場合にのみ機能します:

> User.where( email: 'foo', first_name: 'bar' ).where_values.reduce( :and ).method( :or )                                                
=> #<Method: Arel::Nodes::And(Arel::Nodes::Node)#or>

> User.where( "email = 'foo' and first_name = 'bar'" ).where_values.reduce( :and ).method( :or )                                         
NameError: undefined method `or' for class `String'

文字列とハッシュの両方を使用できるようにするには、これを使用できます:

q1 = User.where( "email = 'foo'" )
q2 = User.where( email: 'bar' )
User.where( q1.arel.constraints.reduce( :and ).or( q2.arel.constraints.reduce( :and ) ) )

確かに、それは見苦しく、日常的にそれを使いたくありません。ここに私が作ったいくつかの#any_of実装があります: https://Gist.github.com/oelmekki/5396826

それをさせましょう:

> q1 = User.where( email: 'foo1' ); true                                                                                                 
=> true

> q2 = User.where( "email = 'bar1'" ); true                                                                                              
=> true

> User.any_of( q1, q2, { email: 'foo2' }, "email = 'bar2'" )
User Load (1.2ms)  SELECT "users".* FROM "users" WHERE (((("users"."email" = 'foo1' OR (email = 'bar1')) OR "users"."email" = 'foo2') OR (email = 'bar2')))

編集:それ以来、私は公開しました 構築を助ける宝石ORクエリ

3
kik

OR条件のスコープを作成するだけです:

scope :author_or_admin, where(['kind = ? OR kind = ?', 'Author', 'Admin'])
1
Unixmonkey

SmartTuple を使用すると、次のようになります。

tup = SmartTuple.new(" OR ")
tup << {:kind => "admin"}
tup << {:kind => "author"}
User.where(tup.compile)

OR

User.where((SmartTuple.new(" OR ") + {:kind => "admin"} + {:kind => "author"}).compile)

あなたは私が偏っていると思うかもしれませんが、私はまだ伝統的なデータ構造操作がこの特定の場合のメソッドチェーンよりもはるかに明確で便利だと考えています。

0
Alex Fortuna

グーグルの人々のために jswanner answer (これは実際に素晴らしいソリューションであり、私を助けた)を拡張するには:

このような範囲を適用できます

scope :with_owner_ids_or_global, lambda{ |owner_class, *ids|
  with_ids = where(owner_id: ids.flatten).where_values.reduce(:and)
  with_glob = where(owner_id: nil).where_values.reduce(:and)
  where(owner_type: owner_class.model_name).where(with_ids.or( with_glob ))
}

User.with_owner_ids_or_global(Developer, 1, 2)
# =>  ...WHERE `users`.`owner_type` = 'Developer' AND ((`users`.`owner_id` IN (1, 2) OR `users`.`owner_id` IS NULL))
0
equivalent8