Kết hợp hai đối tượng ActiveRecord :: Relation


174

Giả sử tôi có hai đối tượng sau:

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

có thể kết hợp hai quan hệ để tạo ra một ActiveRecord::Relationđối tượng chứa cả hai điều kiện không?

Lưu ý: Tôi biết rằng tôi có thể xâu chuỗi các địa điểm để có hành vi này, điều tôi thực sự quan tâm là trường hợp tôi có hai ActiveRecord::Relationđối tượng riêng biệt .


Câu trả lời:


202

Nếu bạn muốn kết hợp sử dụng AND(giao lộ), hãy sử dụng merge:

first_name_relation.merge(last_name_relation)

Nếu bạn muốn kết hợp sử dụng OR(union), hãy sử dụng :or

first_name_relation.or(last_name_relation)

Chỉ trong ActiveRecord 5 +; cho 4.2 cài đặt nơi-hoặc backport.


Nếu tôi muốn kết quả là một ActiveRecord::Relationloại thì sao?
New Alexandria

1
@NewAlexandria Vâng, tất cả các trường hợp, Rails đang nói dối bạn về lớp của đối tượng.
Andrew Marshall

77
Có phiên bản "HOẶC" hợp nhất không?
Arcolye

8
@minohimelf Có lẽ vì đó là kết quả thực tế của việc hợp nhất hai mối quan hệ của bạn. Lưu ý rằng đó mergelà một giao lộ, không phải liên minh.
Andrew Marshall

5
Để tham khảo trong tương lai, #orphương thức đã được thêm vào ActiveRecord :: Relation vào tháng 1 năm 2015 và nó sẽ là một phần của Rails 5.0 , sẽ xuất xưởng vào cuối năm 2015. Nó cho phép sử dụng toán tử OR để kết hợp các mệnh đề WHERE hoặc HAVING. Bạn có thể kiểm tra CHÍNH nếu bạn cần nó trước khi phát hành chính thức. Xem Yêu cầu kéo hợp nhất # 16052 @Arcolye @AndrewMarshall @ Aldo-xoen-Giambelluca
Claudio Floreani

37

Các đối tượng quan hệ có thể được chuyển đổi thành mảng. Điều này phủ nhận việc có thể sử dụng bất kỳ phương thức ActiveRecord nào trên chúng sau đó, nhưng tôi không cần phải làm vậy. Tôi đã làm điều này:

name_relation = first_name_relation + last_name_relation

Ruby 1.9, đường ray 3.2


Cập nhật: đáng tiếc điều này không hợp nhất theo ngày
Daniel Morris

68
Nó không hoạt động như một mảng, nó chuyển đổi mối quan hệ của bạn thành một mảng. Sau đó, bạn không thể sử dụng các phương thức ActiveRecord trên đó như .where () nữa ...
Augustin Riedinger

1
Nếu bạn không quan tâm đến kiểu trả về là mối quan hệ, bạn có thể kết hợp nhiều kết quả với First_name_relation | last_name_relation. "|" điều hành làm việc trên nhiều quan hệ là tốt. Giá trị kết quả là một mảng.
MichaelZ

11
Câu hỏi ban đầu là: "có thể kết hợp hai quan hệ để tạo ra một đối tượng ActiveRecord :: Relation chứa cả hai điều kiện không?" Câu trả lời này trả về một mảng ...
Courtimas

Đây là một giải pháp TUYỆT VỜI cho nhiều trường hợp. Nếu bạn chỉ cần liệt kê các bản ghi để lặp lại (ví dụ: bạn không quan tâm đó là Array hay ActiveRecord :: Relation) và không bận tâm đến hai truy vấn, điều này sẽ giải quyết vấn đề bằng cú pháp đơn giản, rõ ràng. Ước gì tôi có thể +2 nó!
David Hempy

21

mergethực sự không làm việc như OR. Nó chỉ đơn giản là giao điểm ( AND)

Tôi đã vật lộn với vấn đề này để kết hợp với ActiveRecord :: Các đối tượng quan hệ thành một và tôi không tìm thấy bất kỳ giải pháp làm việc nào cho mình.

Thay vì tìm kiếm phương pháp đúng để tạo ra một liên minh từ hai bộ này, tôi tập trung vào đại số của các bộ. Bạn có thể làm điều đó theo cách khác bằng cách sử dụng luật của De Morgan

ActiveRecord cung cấp phương thức hợp nhất (AND) và bạn cũng có thể sử dụng phương thức không hoặc none_of (KHÔNG).

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

Bạn có ở đây (A u B) '= A' ^ B '

CẬP NHẬT: Giải pháp trên là tốt cho các trường hợp phức tạp hơn. Trong trường hợp của bạn smth như thế sẽ là đủ:

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

15

Tôi đã có thể thực hiện điều này, thậm chí trong nhiều tình huống kỳ lạ, bằng cách sử dụng Arel tích hợp của Rails .

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

Điều này hợp nhất cả hai mối quan hệ ActiveRecord bằng cách sử dụng Arel's hoặc .


Hợp nhất, như được đề xuất ở đây, đã không làm việc cho tôi. Nó bỏ tập hợp các đối tượng quan hệ thứ 2 từ kết quả.


2
Đây là câu trả lời tốt nhất, IMO. Nó hoạt động hoàn hảo cho các truy vấn loại OR.
thekingoftruth

Cảm ơn đã chỉ ra điều này, đã giúp tôi rất nhiều. Các câu trả lời khác chỉ hoạt động cho một tập hợp con nhỏ của các trường, nhưng với hơn mười nghìn id trong câu lệnh IN, hiệu suất có thể rất tệ.
onetwopunch

1
@KeesBriggs Haythat không đúng. Tôi đã sử dụng điều này trong tất cả các phiên bản của Rails 4.
6ft Dan

14

Có một viên ngọc gọi là active_record_union có thể là thứ bạn đang tìm kiếm.

Đó là cách sử dụng ví dụ như sau:

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)

Cám ơn vì đã chia sẻ. Thậm chí bốn năm sau bài đăng của bạn, đây là giải pháp duy nhất để tôi tạo ra một liên minh từ đó ransackacts-as-taggable-ontrong khi vẫn giữ ActiveRecordnguyên các trường hợp của tôi .
Benjamin Bojko

Khi sử dụng viên ngọc này, bạn có thể không nhận được ActiveRecord :: Relation được trả về bởi lệnh gọi union () nếu bạn có phạm vi được xác định trên mô hình. Xem Bang hội trong ActiveRecord trong Readme.
dechimp

9

Đây là cách tôi đã "xử lý" nó nếu bạn sử dụng pluck để lấy mã định danh cho từng bản ghi, nối các mảng lại với nhau và cuối cùng thực hiện truy vấn cho các id đã tham gia:

  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)

6

Nếu bạn có một loạt các mối quan hệ Activerecord và muốn hợp nhất tất cả chúng, bạn có thể làm

array.inject(:merge)

array.inject(:merge)đã không làm việc cho một loạt các mối quan hệ Activerecord trong đường ray 5.1.4. Nhưng array.flatten!đã làm.
zhisme

0

Brute buộc nó:

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

"NoMethodError (phương pháp xác định 'stringify_keys' cho # <MyModel: 0x ...) trong Rails3, thậm chí sau khi thay đổi MyModel.none để MyModel.where (1 = 2), kể từ Rails3 không có ' Phương pháp none'
JosephK
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.