合并两个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对象的情况。


Answers:


201

如果要结合使用AND(交叉点),请使用merge

first_name_relation.merge(last_name_relation)

如果要结合使用OR(联合),请使用or

first_name_relation.or(last_name_relation)

仅在ActiveRecord 5+中;对于4.2,请安装where-or backport。


如果我希望结果是ActiveRecord::Relation类型怎么办?
新亚历山大(Alexandria)

1
@NewAlexandria是的,在所有情况下,Rails都会向您撒谎该对象的类。
安德鲁·马歇尔

77
是否有合并的“或”版本?
Arcolye

8
@minohimself可能是因为这是合并两个关系的实际结果。请注意,这merge是一个交叉点,而不是联合。
安德鲁·马歇尔

5
供将来参考,该#or方法已于2015年1月添加到ActiveRecord :: Relation中,并将成为Rails 5.0的一部分,该方法将于2015年末发布。它允许使用OR运算符组合WHERE或HAVING子句。如果需要,可以在正式版本之前检出HEAD。参见合并请求#16052 @Arcolye @AndrewMarshall @ Aldo-xoen-Giambelluca
Claudio Floreani

37

关系对象可以转换为数组。此后否决了对它们使用任何ActiveRecord方法的要求,但是我不需要这样做。我这样做:

name_relation = first_name_relation + last_name_relation

Ruby 1.9,Rails 3.2


更新:可惜这没有按日期合并
Daniel Morris

68
它不充当数组,而是将您的关系转换为数组。那么您将无法再使用
.where

1
如果您不关心返回类型是否为关系,则可以将多个结果与first_name_relation | last_name_relation。“ |” 运算符也可以处理多个关系。结果值是一个数组。
MichaelZ 2015年

10
最初的问题是:“是否可以将两个关系组合在一起以产生一个包含两个条件的ActiveRecord :: Relation对象?” 这个答案返回一个数组……
courtsimas

这是许多情况下的出色解决方案。如果您只需要遍历记录的枚举(例如,您不在乎它是Array还是ActiveRecord :: Relation),并且不介意两个查询的开销,则可以使用显而易见的简单语法解决此问题。希望我能+2!
David Hempy,

21

merge实际上不能像OR。只是交集(AND

我为此问题苦苦挣扎,无法将ActiveRecord :: Relation对象合并为一个对象,但是我没有找到任何可行的解决方案。

我没有寻找从这两个集合创建并集的正确方法,而是关注集合的代数。您可以使用戴摩根定律以不同的方式进行操作

ActiveRecord提供合并方法(AND),也可以使用not method或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')
  )
)

这通过使用Arel 合并了两个ActiveRecord关系。


如此处建议的那样,合并对我不起作用。它从结果中删除了第二组关系对象。


2
IMO,这是最好的答案。它对于OR类型查询完美无缺。
thekingoftruth

感谢您指出这一点,对我帮助很大。其他答案仅适用于一小部分字段,但对于IN语句中的一万多个ID,性能可能非常差。
onetwopunch

1
@KeesBriggs-并非如此。我在滑轨4的所有版本中使用此
6英尺丹

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)

感谢分享。即使在您发表文章后的四年,这也是我创建实例ransackacts-as-taggable-on保持ActiveRecord实例完整的唯一解决方案。
本杰明·博伊科

使用此gem时,如果您在模型上定义了范围,则可能无法获得union()调用返回的ActiveRecord :: Relation。请参阅自述文件中ActiveRecord中的联盟状态。
dechimp '16

9

如果您使用pluck获取每个记录的标识符,将数组连接在一起,然后最后对那些连接的id进行查询,这就是我“处理”它的方式:

  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

如果您有一个activerecord关系数组并想将它们全部合并,则可以执行

array.inject(:merge)

array.inject(:merge)不适用于Rails 5.1.4中的activerecord关系数组。但是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中不具有后'无'方法
JosephK
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.