210

Suppose I have the following two objects:

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

is it possible to combine the two relations to produce one ActiveRecord::Relation object containing both conditions?

Note: I'm aware that I can chain the wheres to get this behavior, what I'm really interested in is the case where I have two separate ActiveRecord::Relation objects.

1

10 Answers 10

265

If you want to combine using AND (intersection), use merge:

first_name_relation.merge(last_name_relation)

If you want to combine using OR (union), use or:

first_name_relation.or(last_name_relation)

Only in ActiveRecord 5+; for 4.2 install the where-or backport.

Sign up to request clarification or add additional context in comments.

14 Comments

@NewAlexandria Yes, all cases, Rails is lying to you about the class of the object.
Is there an "OR" version of merge?
@minohimself Probably because that is the actual result of merging your two relations. Note that merge is an intersection, not union.
For future reference, #or method has been added to ActiveRecord::Relation on Jan 2015, and it will be part of Rails 5.0, which will ship in late 2015. It allows use of the OR operator to combine WHERE or HAVING clauses. You can checkout HEAD if you need it prior the official release. See Merge Pull Request #16052 @Arcolye @AndrewMarshall @Aldo-xoen-Giambelluca
This and #or was exactly what I was looking for. Instead of a gem, I extended ActiveRecord::Relation to support #or in my Rails 4 project. Assuming same model: klass.from("(#{to_sql} union #{other_relation.to_sql}) as #{table_name}")
|
42

Relation objects can be converted to arrays. This negates being able to use any ActiveRecord methods on them afterwards, but I didn't need to. I did this:

name_relation = first_name_relation + last_name_relation

Ruby 1.9, rails 3.2

5 Comments

Update: pity this doesnt merge by date
It doesn't act as an array, it converts your relation to an array. Then you can't use ActiveRecord methods on it like .where() anymore ...
If you don't care about return type being relation, you can combine multiple results with first_name_relation | last_name_relation. The "|" operator works on multiple relations as well. The result value is an array.
Original question was: "is it possible to combine the two relations to produce one ActiveRecord::Relation object containing both conditions?" This answer returns an array...
This is an EXCELLENT solution for many cases. If you only need an enumeration of records to loop through (e.g. you don't care if it's an Array or an ActiveRecord::Relation) and don't mind the overhead of two queries, this solves the problem with obvious, simple syntax. Wish I could +2 it!
24

merge actually doesn't work like OR. It's simply intersection (AND)

I struggled with this problem to combine to ActiveRecord::Relation objects into one and I didn't found any working solution for me.

Instead of searching for right method creating an union from these two sets, I focused on algebra of sets. You can do it in different way using De Morgan's law

ActiveRecord provides merge method (AND) and also you can use not method or none_of (NOT).

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

You have here (A u B)' = A' ^ B'

UPDATE: The solution above is good for more complex cases. In your case smth like that will be enough:

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

1 Comment

none_of actually seems to be part of the third-party active_record_extended gem
17

I've been able to accomplish this, even in many odd situations, by using Rails' built-in Arel.

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

This merges both ActiveRecord relations by using Arel's #or.


Merge, as was suggested here, didn't work for me. It dropped the 2nd set of relation objects from the results.

3 Comments

This is the best answer, IMO. It works flawlessly for OR type queries.
Thanks for pointing this out, helped me out a lot. The other answers only work for a small subset of fields, but for upwards of ten thousand ids in the IN statement, the performance can be very bad.
@KeesBriggs —that's not true. I've used this in all versions of Rails 4.
15

There is a gem called active_record_union that might be what you are looking for.

It's example usages is the following:

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)

2 Comments

Thanks for sharing. Even four years after your post this has been the only solution for me to create a union from ransack and acts-as-taggable-on while keeping my ActiveRecord instances intact.
When using this gem, you may not get an ActiveRecord::Relation returned by the union() call if you have scopes defined on the model. See State of the Union in ActiveRecord in the Readme.
14

This is how I've "handled" it if you use pluck to get an identifier for each of the records, join the arrays together and then finally do a query for those joined ids:

  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)

Comments

6

If you have an array of activerecord relations and want to merge them all, you can do

array.inject(:merge) # like where AND

array.inject(:or) # like where OR

2 Comments

array.inject(:merge) didn't worked for array of activerecord relations in rails 5.1.4. But array.flatten! did.
@zhisme be careful with flatten!, this changes the collection from an ActiveRecord_Relation to an Array. If you want to keep a Relation I would consider inject(:or) > inject(:merge) before using flatten!, depending on your needs
1

In the case where using or is not compactible, I use something like this to get the ActiveRecord_Relation object

User.from("(#{complex_raw_query} UNION #{complex_raw_query}) AS users")

1 Comment

This worked for me, with scopes and .to_sql in the queries (after getting "Relation passed to #or must be structurally compatible. Incompatible values: [:joins]". User.from("(#{scope_returning_users_1.to_sql} UNION #{scope_returning_users_2.to_sql}) AS users")
0

Brute force it:

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

1 Comment

" NoMethodError (undefined method `stringify_keys' for #<MyModel:0x...) in Rails3, even after changing the MyModel.none to MyModel.where(1=2), since Rails3 does not have the 'none' method.
0

Hopefully this is useful to someone out there -- you can make a second query to find the matches by id:

ids = last_name_relation.ids + first_name_relation.ids
User.where(id: ids)

I realize this may not be the most efficient with 3 database requests, but it gets the job done and is simple to understand.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.