Объединить два объекта 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 ИЛИ запрос Хэш-запись
поташин

Ответы:

202

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

first_name_relation.merge(last_name_relation)

Если вы хотите объединить, используя OR(объединение), используйте :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 года. Он позволяет использовать оператор OR для объединения предложений WHERE или HAVING. Вы можете оформить заказ HEAD, если вам это нужно, до официального релиза. См. Запрос на объединение средств # 16052 @Arcolye @AndrewMarshall @ Aldo-xoen-Giambelluca
Клаудио Флориани,
37

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

name_relation = first_name_relation + last_name_relation

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

thatmiddleway
источник
Обновление: жаль, что это не сливается по дате
Даниэль Моррис
68
Он не действует как массив, он конвертирует ваше отношение в массив. Тогда вы больше не сможете использовать методы ActiveRecord, такие как .where () ...
Августин Ридингер
1
Если вас не волнует возвращаемый тип отношения, вы можете объединить несколько результатов с first_name_relation | last_name_relation. "|" Оператор также работает с несколькими отношениями. Результатом является массив.
MichaelZ
11
Первоначальный вопрос был: «Можно ли объединить два отношения для создания одного объекта ActiveRecord :: Relation, содержащего оба условия?» Этот ответ возвращает массив ...
courtimas
Это ОТЛИЧНОЕ решение для многих случаев. Если вам нужно только перечисление записей для циклического прохождения (например, вас не волнует, является ли это массив или ActiveRecord :: Relation) и вы не против накладных расходов на два запроса, это решает проблему с очевидным, простым синтаксисом. Хотел бы я +2 это!
Дэвид Хэмпи
21

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')
Томаш Яшкевич
источник
15

Я был в состоянии сделать это, даже во многих нечетных ситуациях с помощью встроенного в Rails' Arel .

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

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


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

6 футов Дан
источник
2
Это лучший ответ, ИМО. Это работает безупречно для запросов типа OR.
thekingoftruth
Спасибо за указание на это, очень помог мне. Другие ответы работают только для небольшого подмножества полей, но для более десяти тысяч идентификаторов в операторе IN производительность может быть очень плохой.
onetwopunch
1
@KeesBriggs - это не правда. Я использовал это во всех версиях Rails 4.
футовый Дэн
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.
Дечимп
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)
daveomcd
источник
6

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

array.inject(:merge)
stevenspiel
источник
array.inject(:merge)не работал для массива отношений activerecord в рельсах 5.1.4. Но array.flatten!сделал.
Жизме
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