Skip to content

Instantly share code, notes, and snippets.

@avdi
Created March 8, 2012 06:48
Show Gist options
  • Save avdi/1999258 to your computer and use it in GitHub Desktop.
Save avdi/1999258 to your computer and use it in GitHub Desktop.
Failure in two-level has_one...:through chain
# 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!
@joshsusser
Copy link

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.

@armstrjare
Copy link

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!

@austinthecoder
Copy link

Lately I've been avoiding

has_one :thing, :through => :other_thing

in favor of

delegate :thing, :to => :other_thing, :allow_nil => true

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment