Поєднайте два об’єкти ActiveRecord :: Relation


174

Припустимо, у мене є наступні два об'єкти:

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

чи можна поєднати два відношення для отримання одного ActiveRecord::Relationоб'єкта, що містить обидві умови?

Примітка. Я усвідомлюю, що я можу зв'язати ланцюги, щоб отримати таку поведінку, і мене справді цікавить той випадок, коли у мене є два окремих ActiveRecord::Relationоб'єкти.


Можуть допомогти: ActiveRecord АБО запит Hash notation
potashin

Відповіді:


202

Якщо ви хочете поєднати за допомогою AND(перетину), використовуйте merge:

first_name_relation.merge(last_name_relation)

Якщо ви хочете поєднати за допомогою OR(union), використовуйте :or

first_name_relation.or(last_name_relation)

Тільки в ActiveRecord 5+; для 4.2 встановіть де-або резервний порт.


як щодо того, якщо я хочу, щоб результат був ActiveRecord::Relationтипом?
Нова Олександрія

1
@NewAlexandria Так, у всіх випадках Rails бреше тобі про клас об'єкта.
Ендрю Маршалл

77
Чи існує версія "АБО" версія злиття?
Arcolye

8
@minohimself Можливо, тому, що це фактичний результат злиття ваших двох відносин. Зауважте, що mergeце перехрестя, а не союз.
Ендрю Маршалл

5
Для подальшої довідки #orметод був доданий до ActiveRecord :: Relation на січень 2015 року, і він буде частиною Rails 5.0 , який буде доставлений наприкінці 2015 року. Він дозволяє використовувати оператор АБО для комбінування пропозицій WHERE або HAVING. Ви можете замовити HEAD, якщо вам це потрібно до офіційного релізу. Див. Запит на злиття № 16052 @Arcolye @AndrewMarshall @ Aldo-xoen-Giambelluca
Клаудіо Флореані,

37

Об'єкти відношення можна перетворити на масиви. Це заперечує можливість використання будь-яких методів ActiveRecord після цього, але мені цього не потрібно. Я зробив це:

name_relation = first_name_relation + last_name_relation

Рубін 1.9, рейки 3.2


Оновлення: шкода, що це не зливається за датою
Daniel Morris

68
Він не діє як масив, він перетворює ваше відношення до масиву. Тоді ви більше не можете використовувати методи ActiveRecord, як .where () більше ...
Augustin Riedinger

1
Якщо вам не байдуже відношення типу повернення, ви можете поєднати декілька результатів із співвідношенням first_name_relation | last_name_relation. Значок "|" Оператор також працює на кількох відносинах. Значення результату - це масив.
MichaelZ

11
Оригінальним питанням було: "чи можливо комбінувати два відносини для отримання одного об'єкта ActiveRecord :: відношення, що містить обидві умови?" Ця відповідь повертає масив ...
courtimas

Це відмінне рішення для багатьох випадків. Якщо вам потрібно лише перерахування записів, щоб пройти цикл (наприклад, вам не байдуже, чи це масив чи ActiveRecord :: відношення), і ви не заперечуєте накладні два запити, це вирішує проблему з очевидним, простим синтаксисом. Бажаю, щоб я міг +2!
Девід Хемпі

21

mergeнасправді не працює як OR. Це просто перехрестя ( AND)

Я боровся з цією проблемою, щоб об'єднати об’єкти ActiveRecord :: Relation в один, і я не знайшов для мене жодного робочого рішення.

Замість того, щоб шукати правильний метод створення об'єднання з цих двох множин, я зосередився на алгебрі множин. Ви можете зробити це по-різному, використовуючи закон Де Моргана

ActiveRecord надає метод злиття (AND), а також ви можете використовувати не метод або none_of (НЕ).

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

Ви тут (A u B) '= A' ^ B '

ОНОВЛЕННЯ: Наведене вище рішення добре для складніших випадків. У вашому випадку цього подібного буде достатньо:

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

15

Мені вдалося досягти цього, навіть у багатьох дивних ситуаціях, використовуючи вбудований ареал Rails .

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

Це об'єднує обидва відносини ActiveRecord за допомогою Arel або .


Злиття, як було запропоновано тут, не працювало для мене. Це випало з результатів 2-го набору об'єктів відношення.


2
Це найкраща відповідь, ІМО. Він працює бездоганно для запитів типу АБО.
thekingoftruth

Дякую, що вказали на це, мені дуже допомогли. Інші відповіді працюють лише для невеликої підмножини полів, але для більш ніж десяти тисяч ідентифікаторів в операторі IN продуктивність може бути дуже поганою.
onetwopunch

1
@KeesBriggs - це неправда. Я використовував це у всіх версіях Rails 4.
6ft Dan

14

Є дорогоцінний камінь, який називається active_record_union, який може бути тим, що ви шукаєте.

Прикладом звичаїв є наступне:

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)

Дякую, що поділились. Навіть через чотири роки після вашої посади це єдине рішення для мене створити союз ransackі acts-as-taggable-onзберегти мої ActiveRecordінстанції недоторканими.
Бенджамін Божко

Під час використання цього дорогоцінного каміння ви можете не отримати ActiveRecord :: Relation, що повертається викликом union (), якщо у вас визначені області застосування. Дивіться Стан Союзу в ActiveRecord у Readme.
dechimp

9

Ось як я "обробляв" це, якщо ви використовуєте pluck, щоб отримати ідентифікатор для кожної з записів, з'єднайте масиви разом, а потім, нарешті, зробіть запит для цих приєднаних ідентифікаторів:

  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

Якщо у вас є масив активних записів відносин і ви хочете об'єднати їх усі, ви можете зробити

array.inject(:merge)

array.inject(:merge)не працював для масиву активнихзаписів у рейках 5.1.4. Але array.flatten!зробив.
zhisme

0

Грубо змусити це:

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 (не визначений метод` stringify_keys 'для # <MyModel: 0x ...) в Rails3, навіть після зміни MyModel.none на MyModel.where (1 = 2), оскільки у Rails3 немає методу "none".
JosephK
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.