Avoid reloading parent of association?

Consider this situation

class Property < ActiveRecord::Base   has_many :photos end

Now if you reference:

@property.photos[0].property

It reloads the property. I'm looking for a way to suggest the association directly to the associated objects.

I know this example seems contrived, but it's just a simplification. In actuality my Photo model has a method that looks like this:

class Photo < ActiveRecord::Base   def public_filename     File.join("images", property.idx.name, filename)   end end

Not only does this result in n+1 queries, but it actually results in 2n +1, even though I am eager loading all the necessary data up front.

I can think of a lot of ways to solve this, but to cleanly solve it requires somehow suggesting the existing loaded @property object to the photo.property assocation. If there's no way to do this, maybe there should be...

dasil003 wrote:

Consider this situation

class Property < ActiveRecord::Base   has_many :photos end

Now if you reference:

@property.photos[0].property

It reloads the property. I'm looking for a way to suggest the association directly to the associated objects.

I know this example seems contrived, but it's just a simplification. In actuality my Photo model has a method that looks like this:

class Photo < ActiveRecord::Base   def public_filename     File.join("images", property.idx.name, filename)   end end

Not only does this result in n+1 queries, but it actually results in 2n +1, even though I am eager loading all the necessary data up front.

I can think of a lot of ways to solve this, but to cleanly solve it requires somehow suggesting the existing loaded @property object to the photo.property assocation. If there's no way to do this, maybe there should be...

Yes, back associations aren't currently found and set automatically. You can have it done automatically for your association:

class Property < ActiveRecord::Base    has_many :photos    def photos_with_back_links(*params)      photos_without_back_links(*params).each { |p| p.property = self }    end    alias_method_chain :photos, :back_links end

Very cool solution, looking up alias_method_chain was well worth it.

There is one problem when calling a method on the association proxy that doesn't actually return an array of Photos such as:

@property.photos.count

I dug into AssocationProxy and it's subclasses to figure out how best to make this work. I think something like:

class Property < ActiveRecord::Base   has_many :photos   def photos_with_back_links(*params)     result = photos_without_back_links(*params)     result.each { |p| p.set_property_target = self } if result.is_a? (Array)   end   alias_method_chain :photos, :back_links end

will work generally, although it could still choke on methods that return an array that's not a list of photos... is it worth putting a conditional inside the loop?

Actually the other methods don't work at all. This technique can't really solve the problem satisfactorily because the array isn't loaded until a method is called on the proxy that needs it. This alias- chaining is too early.

The method to chain is find_target in AssociationCollection, but that is considerably trickier to pull off...

class Property < ActiveRecord::Base   has_many :photos do     def hydrate       each {|x| x.set_property_target @owner}     end   end end

Property.find(:all, :limit => 10, :include => :photos).each {|x| x.photos.hydrate}

Trevor

dasil003 wrote:

Actually the other methods don't work at all. This technique can't really solve the problem satisfactorily because the array isn't loaded until a method is called on the proxy that needs it. This alias- chaining is too early.

Yes, I'd only considered the case where the unadorned whole collection was being fetched, not the use of proxy methods. I now have a better understanding of the delayed loading the proxy implements by defining the respond_to? method.

The "each" triggers the load of the whole association, so rather than aliasing the association name you'd have to have a separate method for the back-linked version, only used when you want the whole collection.