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.