web-dev-qa-db-ja.com

2つのActiveRecord :: Relationオブジェクトを組み合わせる

次の2つのオブジェクトがあるとします。

first_name_relation = User.where(:first_name => 'Tobias') # ActiveRecord::Relation
last_name_relation  = User.where(:last_name  => 'Fünke') # ActiveRecord::Relation

2つの関係を組み合わせて、両方の条件を含む1つのActiveRecord::Relationオブジェクトを生成することは可能ですか?

注:この動作を取得する場所をチェーン化できることを知っています。本当に興味があるのは、2つの別個のActiveRecord::Relationオブジェクトがある場合です。

160

AND(交差点)を使用して結合する場合は、 merge を使用します。

first_name_relation.merge(last_name_relation)

OR(結合)を使用して結合する場合は、 or を使用します

first_name_relation.or(last_name_relation)

 ActiveRecord 5以降のみ。 4.2では、 where-or バックポートをインストールします。

182
Andrew Marshall

関係オブジェクトは配列に変換できます。これにより、後でそれらに対してActiveRecordメソッドを使用できなくなりますが、必要はありませんでした。これは私がしました:

name_relation = first_name_relation + last_name_relation

Ruby 1.9、Rails 3.2

35
thatmiddleway

mergeは、実際にはORのようには機能しません。単なる交差点(AND

ActiveRecord :: Relationオブジェクトを1つにまとめるためにこの問題に苦労しましたが、実用的な解決策が見つかりませんでした。

これら2つのセットからユニオンを作成する正しい方法を探す代わりに、セットの代数に焦点を当てました。 De Morganの法則 を使用して別の方法でそれを行うことができます

ActiveRecordはマージメソッド(AND)を提供します。また、not methodまたはnone_of(NOT)を使用できます。

search.where.none_of(search.where.not(id: ids_to_exclude).merge(search.where.not("title ILIKE ?", "%#{query}%")))

あなたはここにいます(A u B) '= A' ^ B '

更新:上記のソリューションは、より複雑なケースに適しています。あなたの場合、そのようなsmthは十分でしょう:

User.where('first_name LIKE ? OR last_name LIKE ?', 'Tobias', 'Fünke')
20

Railsの組み込みArelを使用することで、多くの奇妙な状況でもこれを達成することができました。

User.where(
  User.arel_table[:first_name].eq('Tobias').or(
    User.arel_table[:last_name].eq('Fünke')
  )
)

これは、Arelのorを使用して、両方のActiveRecord関係をマージします。


ここで提案されたように、Mergeは私にとってはうまくいきませんでした。結果から関係オブジェクトの2番目のセットを削除しました。

14
6ft Dan

active_record_union というgemがあります。これはあなたが探しているものかもしれません。

使用例は次のとおりです。

current_user.posts.union(Post.published)
current_user.posts.union(Post.published).where(id: [6, 7])
current_user.posts.union("published_at < ?", Time.now)
user_1.posts.union(user_2.posts).union(Post.published)
user_1.posts.union_all(user_2.posts)
13
echo

これは、pluckを使用して各レコードの識別子を取得し、配列を結合し、最後に結合されたIDのクエリを実行する場合の「処理」方法です。

  transaction_ids = @club.type_a_trans.pluck(:id) + @club.type_b_transactions.pluck(:id) + @club.type_c_transactions.pluck(:id)
  @transactions = Transaction.where(id: transaction_ids).limit(100)
8
daveomcd

Activerecord関係の配列があり、それらすべてをマージしたい場合は、次のことができます。

array.inject(:merge)
6
mr.musicman

ブルートフォースそれ:

first_name_relation = User.where(:first_name => 'Tobias') # ActiveRecord::Relation
last_name_relation  = User.where(:last_name  => 'Fünke') # ActiveRecord::Relation

all_name_relations = User.none
first_name_relation.each do |ar|
  all_name_relations.new(ar)
end
last_name_relation.each do |ar|
  all_name_relations.new(ar)
end
0

私はこの問題に何回かつまずきましたが、一般に受け入れられている応答は私にとってはうまくいきませんでした。

受け入れられた答えの提案された解決策は、最もうまくいくように見えました:

first_name_relation.or(last_name_relation)

しかし、これを機能させることは決してできず、「または」に対して「未定義のメソッド」エラーを常に受け​​取ります。

私の解決策:

combined_relation = first_name_relation + (last_name_relation - first_name_relation)

これにより、元のクエリとリレーションオブジェクトとの間のUNIONの結果が基本的に正しいActiveRecordオブジェクトのままになります。

これは@thatmiddlewayが提供する答えに似ていますが、オブジェクトのDISTINCTコレクションを取得するには減算を追加する必要がありました。これにより、結合されたコレクションがARRAYに変換されますが、ビュー内の「各」イテレーターの個々のインスタンスでモデルメソッドを呼び出すことができ、問題を解決しました。

これが私の最初のStackOverflowの回答(gasp!)なので、フィードバックをお待ちしています!

0
Eric Powell