0

I am trying to understand how ActiveRecord Relation merge works since the documentation and examples on it seems lacking. It seems pretty straight-forward with its documentation saying that it returns the intersection of the calling object and the merge parameter 'other'.

I have a simple User class and the appropriate migration

class User < ApplicationRecord
...
end

In most cases it seems like merge behaves exactly as I expect, but upon inspection it behaves unexpectedly. If I set up trivial relations with no overlapping items, I would expect that their intersection would be an empty Relation

relation1 = User.where(id: 1)
relation2 = User.where(id: 2)

relation1.merge(relation2) # returns Relation containing User with id: 2
relation2.merge(relation1) # returns Relation containing User with id: 1

That's the short of it. The longer version of what I am trying to do is that I am using Pundit to set a user's policy scope, in this case all the financial accounts that belong to a user. Then in my controller I have filter routes on the index action for the financial accounts by their type (savings, checking, credit, etc). Therefore I want the user to see only those filtered financial accounts that belong to them, which is the intersection of the two sets: 1) all the financial accounts that are checking accounts, and 2) all the financial accounts that belong to the user. So it seems like merge should do the trick, but it seems to work in a different way than I expect.

Rails 5.1.6
ruby 2.3.3p222
strivedi183
  • 4,749
  • 2
  • 31
  • 38
Andrew
  • 11
  • 3

1 Answers1

1

From what little I can tell from the documentation, since the 'other' relation is an ActiveRecord Relation (as opposed to an Array), it does not strictly produce the intersection. Instead it merges the conditions. In my trivial example, since the conditions are so similar (i.e. where(id: ?), the merge process does not AND the conditions, but rather replaces the calling object's condition with the parameter object's condition.

One way to get around this would be to convert the parameter object to an Array using to_a. In that case, the same merge function does compute the correct intersection.

relation1 = User.where(id: 1)
relation2 = User.where(id: 2)

relation1.merge(relation2.to_a) # returns an empty Array (note: not a Relation)

The downside to this is that the query is not done completely in the database and is therefore less efficient.

This also seems to explain why it does work as expected more often than not for my more complex merges. More complex merges would have more complex conditions that would rarely be similar enough to be overridden/replaced as in the simpler case.

Andrew
  • 11
  • 3