Unexpected behavior with ActiveRecord includes

I use the AR includes method to execute the LEFT OUTER JOIN between the User and Building objects, where the User may or may not have the Building association:

 users = User.includes(:building).references(:buildings) 

Since I use references , any related building objects will be loaded.

My assumption was that after that I could iterate over the list of users and check if the building is associated with it, not related to additional requests, but I see that in fact whenever I try to access the property of the user's building that doesn't have one, AR makes another SQL call to try to restore this building (although on subsequent attempts it will just return zero).

These requests are clearly redundant, since the association would be loaded during the initial merge and seems to defeat the whole goal of active loading with inclusion / links, since now I look through N times the number of requests equal to the number of empty associations.

 users.each do | user | # This will trigger a new query when building is not present: # SELECT "buildings".* FROM "buildings" WHERE "buildings"."address" = $1 LIMIT 1 [["address", "123 my street"]] if user.building puts 'User has building' else puts 'User has no building' end end 

User Class:

 class User < ActiveRecord::Base belongs_to :building, foreign_key: 'residence_id' end 

Is there a way to check for user associations without causing additional queries?


ON RAILS 4.2.0 / POSTGRES


UPDATE:

Thanks @BoraMa for matching this test . It looks like we get different behavior in recent versions of Rails:

OUTPUT (RAILS 4.2.0):

 User 1 has building User 2 has building User 3 has no building D, [2016-05-26T11:48:38.147316 #11910] DEBUG -- : Building Load (0.2ms) SELECT "buildings".* FROM "buildings" WHERE "buildings"."id" = $1 LIMIT 1 [["id", 123]] User 4 has no building 

OUTPUT (RAILS 4.2.6)

 User 1 has building User 2 has building User 3 has no building User 4 has no building 

OUTPUT (RAILS 5.0.0)

 User 1 has building User 2 has building User 3 has no building User 4 has no building 

Take aways:

  • This question was limited to "hanging foreign keys" (for example, the residence_id column is not zero, but there is no corresponding building object) "(THANKS @FrederickCheung)
  • Problem resolved with Rails 4.2.6
+6
source share
3 answers

It looks like you got a bit error in Active Record that was fixed on rails 4.2.3.

In the case when the column was zero, Active Record already knows that you do not even need to try to load the associated object. The remaining cases were affected by this error.

+3
source

Looks like a typo, pay attention to building instead of buildings : User.includes(:building).references(:buildings)

This should trigger a large query that uses the AS tX_rY format for each association and table.

0
source

It seems that with rails 4.1 there are potential clashes with how the implied #includes should be, see the following open issue .

This code is not checked for syntax, but I would try to do two approaches:

1 / Make impatient download implicit

 users = User.eager_load(:building).preload(:buildings) 

2 / Separate the two types of users who are attached to the building, which means that you are not even trying to preload the building by deleting the unnecessary one.

 users = User.includes(:building).where.not(residence_id: nil).references(:buildings) users.each do | user| puts "User has building: #{user} #{user.building}" end # No additional references needed to be eager-loaded. users = User.where(residence_id: nil) users.each do | user | puts "#{User} has no building." end 
0
source

All Articles