Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected behavior with ActiveRecord includes

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

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

Since I'm using references, any associated Building objects will be eager loaded.

My expectation was that I would then be able to iterate through the list of users, and check whether a user had a building associated with them without triggering additional queries, but I see that in fact whenever I try to access the building property of a user that doesn't have one, AR makes another SQL call to try and retrieve that building (though on subsequent tries it will just return nil).

These queries are obviously redundant as the association would have been loaded during the initial join, and seems to defeat the whole purpose of eager loading with includes/references, as now I'm looking at N times the number of queries 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 the presence of the users' building association without triggering extra queries?


ON RAILS 4.2.0 / POSTGRES


UPDATE:

Thank you @BoraMa for putting together this test. Looks like we're getting different behavior across recent Rails versions:

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 issue was limited to "dangling foreign keys (ie the residence_id column is not nil but there is no corresponding building object)" (THANKS @FrederickCheung)
  • The issue has been resolved as of Rails 4.2.6
like image 890
Yarin Avatar asked May 23 '16 21:05

Yarin


People also ask

What does ActiveRecord base mean?

ActiveRecord::Base indicates that the ActiveRecord class or module has a static inner class called Base that you're extending.

What is an ActiveRecord object?

In Active Record, objects carry both persistent data and behavior which operates on that data. Active Record takes the opinion that ensuring data access logic as part of the object will educate users of that object on how to write to and read from the database.

What is ActiveRecord in Ruby on Rails?

What is ActiveRecord? ActiveRecord is an ORM. It's a layer of Ruby code that runs between your database and your logic code. When you need to make changes to the database, you'll write Ruby code, and then run "migrations" which makes the actual changes to the database.


1 Answers

Sounds like you got bit by a bug in Active Record, that was fixed in rails 4.2.3.

In the case where the column was nil Active Record already knows that it doesn't even need to try loading the associated object. The remaining cases were the ones impacted by this bug

like image 187
Frederick Cheung Avatar answered Oct 10 '22 21:10

Frederick Cheung