Preloaded associations are filtered by conditions on the same table

Updated . Posted . Visible to the public. Repeats.

When you eagerly load an association list using the .include option, and at the same time have a .where on an included table, two things happen:

  1. Rails tries to load all involved records in a huge single query spanning multiple database tables.
  2. The preloaded association list is filtered by the where condition, even though you only wanted to use the where condition to filter the containing model.

The second case's behavior is mostly unexpected, because pre-loaded associations usually don't care about the circumstances under which their containing model was found.

Example

Take this class:

class Activity
  has_many :users
end

Here activity 42 has four users:

activity = Activity.find(42)
activity.users.ids
# => [1, 2, 3, 4]

Let's say we want to do the same on all activities that belong to a user. To this, we scope on Activity.
Note how the activity from above now suddenly no longer contains any other users:

activity = Activity.
  .includes(:users)
  .where(users: { id: [4] })
  .find(42)
  
activity.users.ids # here happens the unexpected
# => [4]

By reloading the object, its full list of associated users is restored:

activity.reload.users.ids
# => [1, 2, 3, 4]

Or you can reset the association cache:

activity.users.reset # newer Rails
activity.users(true) # old Rails

In newer Rails versions you should prefer to use joins and then preload if necessary (which will trigger a second query):

# Join for the condition, but do not preload
activities = Activity.joins(:users).where(users: { id: [4] })

# Preload associations with a second query on users;
# Does not make a join.
activities = activities.preload(:users)
Arne Hartherz
Last edit
Emanuel
Keywords
join, joins, has_many
License
Source code in this card is licensed under the MIT License.
Posted by Arne Hartherz to makandra dev (2010-12-09 14:08)