Avoid reloading parent of association?


#1

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…


#2

dasil003 wrote:

It reloads the property. I’m looking for a way to suggest the

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


We develop, watch us RoR, in numbers too big to ignore.


#3

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?


#4

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…


#5

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

On 6/13/07, dasil003 removed_email_address@domain.invalid wrote:

Very cool solution, looking up alias_method_chain was well worth it.
has_many :photos
conditional inside the loop?

Now if you reference:
def public_filename
there should be…
end


We develop, watch us RoR, in numbers too big to ignore.

Trevor S.
http://somethinglearned.com


#6

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.


We develop, watch us RoR, in numbers too big to ignore.