Gabungkan dua objek ActiveRecord :: Relation

174

Misalkan saya memiliki dua objek berikut:

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

Apakah mungkin untuk menggabungkan kedua relasi untuk menghasilkan satu ActiveRecord::Relationobjek yang mengandung kedua kondisi?

Catatan: Saya sadar bahwa saya bisa rantai di mana saja untuk mendapatkan perilaku ini, apa yang saya benar-benar tertarik adalah kasus di mana saya memiliki dua ActiveRecord::Relationobjek terpisah .

Patrick Klingemann
sumber
Mungkin bisa membantu: ActiveRecord ATAU permintaan Notasi Hash
potashin

Jawaban:

202

Jika Anda ingin menggabungkan menggunakan AND(persimpangan), gunakan merge:

first_name_relation.merge(last_name_relation)

Jika Anda ingin menggabungkan menggunakan OR(gabungan), gunakan :or

first_name_relation.or(last_name_relation)

Hanya di ActiveRecord 5+; untuk 4.2, instal di mana-atau backport.

Andrew Marshall
sumber
bagaimana kalau saya ingin hasilnya menjadi ActiveRecord::Relationtipe?
New Alexandria
1
@NewAlexandria Ya, semua kasus, Rails berbohong kepada Anda tentang kelas objek.
Andrew Marshall
77
Apakah ada versi penggabungan "ATAU"?
Arcolye
8
@minohimself Mungkin karena itulah hasil aktual dari penggabungan dua relasi Anda. Perhatikan bahwa itu mergeadalah persimpangan, bukan penyatuan.
Andrew Marshall
5
Untuk referensi di masa mendatang, #ormetode telah ditambahkan ke ActiveRecord :: Relation pada Jan 2015, dan itu akan menjadi bagian dari Rails 5.0 , yang akan dikirimkan pada akhir 2015. Ini memungkinkan penggunaan operator OR untuk menggabungkan klausa WHERE atau HAVING. Anda dapat checkout KEPALA jika Anda membutuhkannya sebelum rilis resmi. Lihat Permintaan Gabung Tarik # 16052 @Arcolye @AndrewMarshall @ Aldo-xoen-Giambelluca
Claudio Floreani
37

Objek relasi dapat dikonversi menjadi array. Ini meniadakan kemampuan untuk menggunakan metode ActiveRecord setelahnya, tetapi saya tidak perlu melakukannya. Saya melakukan ini:

name_relation = first_name_relation + last_name_relation

Ruby 1.9, rails 3.2

jalan tengah itu
sumber
Perbarui: kasihan ini tidak bergabung berdasarkan tanggal
Daniel Morris
68
Itu tidak bertindak sebagai sebuah array, itu mengubah hubungan Anda ke sebuah array. Maka Anda tidak dapat menggunakan metode ActiveRecord di atasnya seperti .where () lagi ...
Augustin Riedinger
1
Jika Anda tidak peduli tentang hubungan tipe pengembalian, Anda dapat menggabungkan beberapa hasil dengan first_name_relation | last_name_relation. "|" Operator bekerja pada banyak hubungan juga. Nilai hasil adalah array.
MichaelZ
11
Pertanyaan awal adalah: "apakah mungkin untuk menggabungkan dua relasi untuk menghasilkan satu ActiveRecord :: Objek relasi yang mengandung kedua kondisi?" Jawaban ini mengembalikan array ...
courtimas
Ini adalah solusi yang SANGAT BAIK untuk banyak kasus. Jika Anda hanya perlu enumerasi catatan untuk mengulang (mis. Anda tidak peduli apakah itu Array atau ActiveRecord :: Relation) dan tidak keberatan dengan overhead dua kueri, ini menyelesaikan masalah dengan sintaksis yang jelas dan sederhana. Seandainya aku bisa +2 itu!
David Hempy
21

mergesebenarnya tidak bekerja OR. Ini hanya persimpangan ( AND)

Saya kesulitan dengan masalah ini untuk menggabungkan ke objek ActiveRecord :: Relation menjadi satu dan saya tidak menemukan solusi yang berfungsi untuk saya.

Alih-alih mencari metode yang tepat membuat penyatuan dari dua set ini, saya fokus pada aljabar set. Anda dapat melakukannya dengan cara yang berbeda menggunakan hukum De Morgan

ActiveRecord menyediakan metode gabungan (DAN) dan Anda juga dapat menggunakan metode tidak atau none_dari (TIDAK).

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

Anda miliki di sini (A u B) '= A' ^ B '

UPDATE: Solusi di atas baik untuk kasus yang lebih kompleks. Dalam kasus Anda, pertanda seperti itu akan cukup:

User.where('first_name LIKE ? OR last_name LIKE ?', 'Tobias', 'Fünke')
Tomasz Jaśkiewicz
sumber
15

Saya sudah bisa mencapai ini, bahkan dalam banyak situasi aneh, dengan menggunakan Arel bawaan Rails .

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

Ini menggabungkan kedua hubungan ActiveRecord dengan menggunakan Arel atau .


Penggabungan, seperti yang disarankan di sini, tidak berhasil untuk saya. Itu menjatuhkan set objek relasi ke-2 dari hasil.

6ft Dan
sumber
2
Ini jawaban terbaik, IMO. Ia bekerja dengan sempurna untuk kueri tipe ATAU.
thekingoftruth
Terima kasih telah menunjukkan ini, banyak membantu saya. Jawaban lainnya hanya berfungsi untuk sebagian kecil bidang, tetapi untuk lebih dari sepuluh ribu id dalam pernyataan IN, kinerjanya bisa sangat buruk.
onetwopunch
1
@KeesBriggs — itu tidak benar. Saya telah menggunakan ini di semua versi Rails 4.
6ft Dan
14

Ada permata bernama active_record_union yang mungkin Anda cari.

Contoh penggunaannya adalah sebagai berikut:

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)
gema
sumber
Terima kasih telah berbagi. Bahkan empat tahun setelah posting Anda ini telah menjadi satu-satunya solusi bagi saya untuk membuat persatuan ransackdan acts-as-taggable-onmenjaga ActiveRecordinstans saya tetap utuh.
Benjamin Bojko
Saat menggunakan permata ini, Anda mungkin tidak mendapatkan panggilan ActiveRecord :: Relation yang dikembalikan oleh union () jika Anda memiliki cakupan yang ditentukan pada model. Lihat Status Serikat di ActiveRecord di Readme.
dechimp
9

Ini adalah cara saya "menanganinya" jika Anda menggunakan pluck untuk mendapatkan pengidentifikasi untuk masing-masing rekaman, bergabung dengan array bersama dan akhirnya melakukan kueri untuk id yang bergabung:

  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)
daveomcd
sumber
6

Jika Anda memiliki array hubungan activerecord dan ingin menggabungkan semuanya, Anda bisa melakukannya

array.inject(:merge)
stevenspiel
sumber
array.inject(:merge)tidak bekerja untuk array hubungan activerecord di rel 5.1.4. Tapi itu array.flatten!benar.
zhisme
0

Paksa itu:

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
Richard Grossman
sumber
"NoMethodError (metode yang tidak ditentukan` stringify_keys 'untuk # <MyModel: 0x ...) di Rails3, bahkan setelah mengubah MyModel.none ke MyModel.where (1 = 2), karena Rails3 tidak memiliki metode' none '.
JosephK