Объединить два объекта ActiveRecord :: Relation - PullRequest
157 голосов
/ 03 марта 2012

Предположим, у меня есть два следующих объекта:

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.

Ответы [ 9 ]

175 голосов
/ 03 марта 2012

Если вы хотите объединить, используя AND (пересечение), используйте merge:

first_name_relation.merge(last_name_relation)

Если вы хотите объединить, используя OR (объединение), используйте or:

first_name_relation.or(last_name_relation)

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

35 голосов
/ 06 марта 2013

Объекты отношений могут быть преобразованы в массивы.Это сводит на нет возможность использовать любые методы ActiveRecord впоследствии, но мне это не нужно.Я сделал это:

name_relation = first_name_relation + last_name_relation

Рубин 1.9, рельсы 3.2

19 голосов
/ 11 декабря 2014

merge на самом деле не работает как OR.Это просто пересечение (AND)

Я боролся с этой проблемой, чтобы объединить объекты ActiveRecord :: Relation в один, и я не нашел никакого рабочего решения для себя.

Вместо того, чтобы искать правильный метод создания объединения из этих двух множеств, я сосредоточился на алгебре множеств.Вы можете сделать это по-другому, используя закон Де Моргана

ActiveRecord предоставляет метод слияния (AND), а также вы можете использовать метод not или 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 '

ОБНОВЛЕНИЕ: Решение, приведенное выше, подходит для более сложных случаев.В вашем случае чего-то такого будет достаточно:

User.where('first_name LIKE ? OR last_name LIKE ?', 'Tobias', 'Fünke')
14 голосов
/ 06 февраля 2015

Я смог сделать это, даже во многих странных ситуациях, используя встроенный в Rails Arel .

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

Это объединяетоба отношения ActiveRecord с использованием Ареля или .


Слияние, как было предложено здесь, у меня не сработало.Он отбросил второй набор объектов отношений из результатов.

13 голосов
/ 12 мая 2015

Существует камень под названием 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)
8 голосов
/ 05 сентября 2016

Вот как я «обработал» это, если вы используете 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 голосов
/ 04 июля 2015

Если у вас есть массив отношений activerecord и вы хотите объединить их все, вы можете сделать

array.inject(:merge)
0 голосов
/ 29 марта 2018

Я сталкивался с этой проблемой несколько раз, и общепринятые ответы никогда не работали для меня.

Предложенное решение в принятом ответе показалось, что оно будет работать лучше всего:

first_name_relation.or(last_name_relation)

Но я никогда не мог заставить это работать и всегда получал ошибку «неопределенный метод» для «или».

Мое решение:

combined_relation = first_name_relation + (last_name_relation - first_name_relation)

По сути, это дает мне результаты UNION между исходными запросами и объектами отношений, которые все еще являются правильными объектами ActiveRecord.

Это похоже на ответ, предоставленный @thatmiddleway, хотя мне нужно было добавить вычитание, чтобы получить коллекцию объектов DISTINCT. Это действительно преобразует объединенную коллекцию в ARRAY, но я все еще могу вызывать методы модели для отдельных экземпляров в «каждом» итераторе, на мой взгляд, что решило мою проблему.

Это мой первый ответ на StackOverflow (задыхаясь!), Поэтому я приветствую ваши отзывы!

0 голосов
/ 21 июля 2015

Грубая сила это:

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
...