-
-
Save avdi/1999258 to your computer and use it in GitHub Desktop.
| # This is in a Rails 3.0 app | |
| # Class names changed to protect the innocent | |
| class NeckBone | |
| has_one :back_bone | |
| has_one :thigh_bone, :through => :back_bone | |
| has_one :knee_bone, :through => :thigh_bone | |
| delegate :walk_around, :to => :knee_bone | |
| end | |
| # assume back_bone exists and has a thigh_bone etc. | |
| neck_bone = NeckBone.new(:back_bone => back_bone) | |
| neck_bone.walk_around | |
| # => NeckBone#walk_around delegated to knee_bone.walk_around, | |
| # but knee_bone is nil: #<NeckBone id: nil, back_bone_id: 3, | |
| # created_at: nil, updated_at: nil> | |
| neck_bone.back_bone.thigh_bone.knee_bone.walk_around # => Works! |
Interesting.
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/association.rb#L138
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/association.rb#L165-167
Looks like... if it's not saved, it's the ActiveRecord association proxy. Essentially, I'm assuming you've assigned back_bone to an existing record. However, that doesn't automatically propagate through to loading the knee_bone association target (which is delegated to). ActiveRecord will only 'load' an association target if the record is persisted or there is a foreign key present for the association on the record. So yeah, looks like it doesn't walk up through the chain of through associations looking for a loaded target to then walk back down through, although that would be cool!
Lately I've been avoiding
has_one :thing, :through => :other_thing
in favor of
delegate :thing, :to => :other_thing, :allow_nil => true
If you have not yet saved the neck_bone object, it has no PK and the thigh_bone record in the db won't have a neck_bone_id. I don't think Arel is clever enough to handle a mix of saved and unsaved records in that string of throughs. But going one step at a time works because it's only one kind of record at a time.