ActiveRecord hook to implement an automatic eager loading feature

Hello guys,

The Salsify Engineering team made a gem to automatic eager load associations on rails, take a look here: http://blog.salsify.com/engineering/automatic-eager-loading-rails

If you want to avoid to read the entire post, they made this:

blogs = Blogs.limit(5).to_a

SELECT * FROM blogs LIMIT 5

blogs.each { |blog| blog.posts.to_a }

SELECT * FROM posts WHERE blog_id IN (1,2,3,4,5)

Which I think that may make a huge sense for a lot of applications.

But active record seems to not provide a hook to do overwrite internals like that, they had to monkey patch a lot of methods here: https://github.com/salsify/goldiloader/blob/master/lib/goldiloader/active_record_patches.rb

I’m not pretty comfortable with active record source code but my suggestion is to make a accessor or a hook to use a custom collection proxy class.

Currently when we call blog.posts we receive a ActiveRecord::Associations::CollectionProxy object.

What if we can change active record to use a custom builder object, or a lambda, like that?

ActiveRecord::Base.association_builder = lambda do |source, instance, association|

SharedAssociationProxy.build(source: source, instance: instance, association: association)

end

class SharedAssociationProxy < ActiveRecord::Associations::CollectionProxy

def self.build(source:, instance:, association:)

registry[source, association] ||= new(source.klass, association)
registry[source, association].append(instance)

end

def to_a(instance)

objects.select do |object|
  object[reflection.primary_key] == object.id
end

end

protected

def objects

@objects ||= reflection.klass.where(reflection.primary_key => instances.map(&:id))

end

end

blogs = Blogs.limit(5).to_a

blogs.first.posts # will use the blogs as source since it fetched a collection

=> SharedAssociationProxy.build(source: blogs, instance: blog, association: :posts)

blog = Blog.first

blog.posts # will use the blog as source since it was the only fetched

=> SharedAssociationProxy.build(source: blog, instance: blog, association: :posts)

Like I said, I’m not pretty comfortable with active record source code, then I don’t know the exactly details that need to be passed to the builder, lets focus just on the concept.

If we want a toggle option like the fully_load made by Salsify guys, we can just check for it on the lambda or the builder.

About the to_a method, it would be necessary to receive the model instance that is calling the to_a method to do the necessary work, it would happen for all methods that can have a custom implementation: first, last, to_a, etc.

What do you guys think about that?

Cheers,

Gabriel Sobrinho

gabrielsobrinho.com

Another interesting use would be on octopus which would allow us to send the association query to the right shard: https://github.com/tchandy/octopus

We currently have a lot of monkey patches to make it work too: https://github.com/tchandy/octopus/blob/master/lib/octopus/association_shard_tracking.rb