203

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.

Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
Patrick Klingemann
  • 8,884
  • 4
  • 44
  • 51

10 Answers10

253

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.

Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
  • how about if I want the result to be an `ActiveRecord::Relation` type? – New Alexandria Dec 28 '12 at 05:43
  • @NewAlexandria `merge` returns an `ActiveRecord::Relation`… – Andrew Marshall Dec 28 '12 at 08:28
  • [not in all cases](http://stackoverflow.com/questions/14065425/why-are-rails-model-association-results-not-naturally-activerecordrelations) – New Alexandria Dec 28 '12 at 17:16
  • 1
    @NewAlexandria Yes, all cases, Rails is lying to you about the class of the object. – Andrew Marshall Dec 28 '12 at 18:06
  • 77
    Is there an "OR" version of merge? – Arcolye Sep 13 '13 at 17:17
  • @Arcolye, there's an open pull request on rails with this option being discussed https://github.com/rails/rails/pull/9052 – jstim Nov 06 '13 at 22:22
  • 8
    @minohimself Probably because that is the actual result of merging your two relations. Note that `merge` is an intersection, not union. – Andrew Marshall Dec 24 '13 at 23:52
  • you can actually .merge! as well if you wanted to do something like this - query.merge!(second_query) - and have query return both conditions – Matt Ramirez Feb 12 '14 at 16:40
  • @Frozenna You can do that but you have to have done a [`joins`](http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-joins) between them first. E.g. `cars.joins(:motors).where(Motor.some_scope)`. – Andrew Marshall Feb 12 '14 at 21:38
  • As a temporary solution, I use `first_name_relation | last_name_relation` for OR relations. Note that this will of course make two separate requests and transform the result in an Array. – Raf Apr 02 '15 at 07:15
  • 5
    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](https://github.com/rails/rails/commit/9e42cf019f2417473e7dcbfcb885709fa2709f89) @Arcolye @AndrewMarshall @Aldo-xoen-Giambelluca – Claudio Floreani Sep 24 '15 at 20:13
  • Is **first_name_relation.or(last_name_relation)** equivalent to UNION or UNION ALL? – Imran Ahmad Apr 01 '17 at 15:32
  • @Imran Neither, it combines the `WHERE` clauses using an `OR`. The docs linked in my answer contain an example SQL query generated using `or`. – Andrew Marshall Apr 01 '17 at 18:21
  • 2
    [This](https://stackoverflow.com/a/16868735/5909281) 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}")` – M. Wyatt Sep 28 '18 at 19:08
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

pizza247
  • 3,869
  • 7
  • 33
  • 47
  • Update: pity this doesnt merge by date – Daniel Morris Jul 05 '13 at 18:21
  • 79
    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 ... – Augustin Riedinger Jul 17 '13 at 13:17
  • 2
    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. – MichaelZ Jun 10 '15 at 13:01
  • 14
    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... – courtsimas Aug 03 '17 at 19:02
  • 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! – David Hempy Aug 29 '18 at 13:47
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')
roob
  • 1,116
  • 7
  • 11
  • `none_of` actually seems to be part of the third-party [active_record_extended gem](https://github.com/GeorgeKaraszi/ActiveRecordExtended) – Wolfgang Sep 01 '22 at 20:28
16

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.

6ft Dan
  • 2,365
  • 1
  • 33
  • 46
  • 2
    This is the best answer, IMO. It works flawlessly for OR type queries. – thekingoftruth Mar 07 '15 at 01:24
  • 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. – onetwopunch Apr 20 '15 at 23:24
  • 1
    @KeesBriggs —that's not true. I've used this in all versions of Rails 4. – 6ft Dan Oct 03 '17 at 23:28
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)
echo
  • 900
  • 1
  • 9
  • 17
  • 1
    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. – Benjamin Bojko Sep 15 '15 at 16:51
  • 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. – dechimp Dec 01 '16 at 19:24
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)
daveomcd
  • 6,367
  • 14
  • 83
  • 137
6

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

array.inject(:merge)
stevenspiel
  • 5,775
  • 13
  • 60
  • 89
  • `array.inject(:merge)` didn't worked for array of activerecord relations in rails 5.1.4. But `array.flatten!` did. – zhisme Mar 22 '18 at 14:17
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")
pk-n
  • 568
  • 4
  • 11
  • 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")` – caroline Jul 25 '23 at 14:37
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
Richard Grossman
  • 741
  • 1
  • 5
  • 10
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.