-
-
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.