has_one :through - ArgumentError: Unknown key(s): through

01 Jun 2006

Problem:

You’re getting something that says “ArgumentError: Unknown key(s): through” when you’re using a has_one :something, :through => :something else code.

Explanation:

The :through option for Rails join models is really, really handy. Basically, it allows you to turn guy.thing.places into guy.places without having to change anything in the database.

How the models would be setup to allow for the more tedious way to associate places with a guy.

    class Guy < ActiveRecord::Base
      has_one :thing
    end

    class Thing < ActiveRecord::Base
      belongs_to :guy
      has_many   :places
    end

    class Place < ActiveRecord::Base
      belongs_to :thing
    end

The only alteration needed is turning the Guy class into:

    class Guy < ActiveRecord::Base
      has_one  :thing
      has_many :places, :through => :thing
    end

For a better example of how to use :through check out Ariejan on Rails

The problem with this, and I don’t know why this is, is that this doesn’t work with has_one - only has_many! If we were to change Thing to only have one Place we would then alter the joins thusly:

    class Guy < ActiveRecord::Base
      has_one  :thing
      has_one  :place, :through => :thing
    end

    class Thing < ActiveRecord::Base
      belongs_to :guy
      has_one    :place
    end

And our code would fail! The above code will barf out an ArgumentError: Unknown key(s): through error.

Solution:

You can use a has_many join and make it act like a has_one join. Just use:

    class Guy < ActiveRecord::Base
      has_one  :thing
      has_many :place, :through => :thing
    end

Then, when you call guy.place you’ll get an array (with just one element) of places. You can use guy.place.first to replace guy.thing.place

  • bob said: er... guy.place.first isn't any better than guy.thing.place. clever. but not a solution.
  • Danger said: Totally :-) I wrote this and immediately realized that there's a reason nobody ever wrote has_one :through - 'cause there's no need! You can just use the relationship chain to get what you want or define a shortcut in your model. But if somebody really wanted to use has_many :through (for some ridiculous purpose) they still could - they'd just get an array. I think I'll leave this assinine post up just to remind myself not to write stupid shit.

Please if you found this post helpful or have questions.