thoughtbot/shoulda

should have_one(:item).through(:bla) doesn't work

Closed this issue · 7 comments

While have_many(:items).through(:bla) is recognised by Shoulda, should have_one(:item).through(:bla) doesn't seem to be implemented yet, it results in this error:

  1) Item 
     Failure/Error: it { should have_one(:item).through(:bla) }
       Expected Document to have a has_one association called activity (Item does not have any relationship to item)

It works for me. Your model is missing has_one :item. That's what the failure tells me, at least.

However, if you can paste a failing test, I'll gladly re-open this. This is my spec: https://github.com/thoughtbot/shoulda-matchers/blob/master/spec/shoulda/active_record/association_matcher_spec.rb#L446-L469

Right, it was an error on my side, sorry for wasting your time.

But when we're just at it, here's another question: Why does Shoulda always insist on having the optional 2nd association also defined when checking on a :through relation?

# Works with Shoulda
has_one :document, :through => :activity_document
has_one :activity_document

# Doesn't with Shoulda, results in Expected Activity to have a has_one association called document (Activity does not have any relationship to activity_document)
has_one :document, :through => :activity_document

I asked a very similar question here about has_many :through. Thanks for explaining!

The second association is not optional, so far as I can tell. Here's a quick collection of models I made:

class Magazine < ActiveRecord::Base
  has_one :document, :through => :activity_document
end

class Document < ActiveRecord::Base
  belongs_to :activity_document
end

class ActivityDocument < ActiveRecord::Base
  belongs_to :magazine
  has_one :document
end

And here's what script/rails c tells me:

irb(main):004:0> m = Magazine.create!
   (0.0ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "magazines" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", Tue, 10 Jul 2012 13:05:08 UTC +00:00], ["updated_at", Tue, 10 Jul 2012 13:05:08 UTC +00:00]]
   (2.7ms)  commit transaction
=> #<Magazine id: 4, created_at: "2012-07-10 13:05:08", updated_at: "2012-07-10 13:05:08">
irb(main):005:0> m.document
ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :activity_document in model Magazine
    from /var/lib/gems/1.8/gems/activerecord-3.2.6/lib/active_record/reflection.rb:501:in `check_validity!'
    from /var/lib/gems/1.8/gems/activerecord-3.2.6/lib/active_record/associations/association.rb:26:in `initialize'
    from /var/lib/gems/1.8/gems/activerecord-3.2.6/lib/active_record/associations.rb:157:in `new'
    from /var/lib/gems/1.8/gems/activerecord-3.2.6/lib/active_record/associations.rb:157:in `association'
    from /var/lib/gems/1.8/gems/activerecord-3.2.6/lib/active_record/associations/builder/association.rb:44:in `document'
    from (irb):42

Whereas adding the has_one :activity_document to Magazine fixes it.

Okay, maybe it's different to has_many :through? Because there the 2nd association is not needed, but Shoulda still insists on it. Why's that?

I asked a related question on StackOverflow.

Same problem with a has_many:

class Magazine < ActiveRecord::Base
  has_many :documents, :through => :activity_document
end

class Document < ActiveRecord::Base
  belongs_to :activity_document
end

class ActivityDocument < ActiveRecord::Base
  belongs_to :magazine
  has_many :document
end

And similar output:

irb(main):007:0> m = Magazine.create!
   (0.0ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "magazines" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", Tue, 10 Jul 2012 13:24:54 UTC +00:00], ["updated_at", Tue, 10 Jul 2012 13:24:54 UTC +00:00]]
   (2.5ms)  commit transaction
=> #<Magazine id: 3, created_at: "2012-07-10 13:24:54", updated_at: "2012-07-10 13:24:54">
irb(main):008:0> m.documents
ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :activity_documents in model Magazine
    from /var/lib/gems/1.8/gems/activerecord-3.2.6/lib/active_record/reflection.rb:501:in `check_validity!'
    from /var/lib/gems/1.8/gems/activerecord-3.2.6/lib/active_record/associations/association.rb:26:in `initialize'
    from /var/lib/gems/1.8/gems/activerecord-3.2.6/lib/active_record/associations/collection_association.rb:24:in `initialize'
    from /var/lib/gems/1.8/gems/activerecord-3.2.6/lib/active_record/associations/has_many_through_association.rb:10:in `initialize'
    from /var/lib/gems/1.8/gems/activerecord-3.2.6/lib/active_record/associations.rb:157:in `new'
    from /var/lib/gems/1.8/gems/activerecord-3.2.6/lib/active_record/associations.rb:157:in `association'
    from /var/lib/gems/1.8/gems/activerecord-3.2.6/lib/active_record/associations/builder/association.rb:44:in `documents'
    from (irb):8

Please paste some code with an unexpected test failure/success next time.

Thanks,
-Mike

Strange, so why do people on StackOverflow tell me something different?

http://stackoverflow.com/questions/11412695/has-many-through-why-do-i-always-need-2-associations-for-each-of-the-3-models/11413304#11413304

I tried it out myself now, and I got the same result like you (I'm sorry for not having tried it before and letting you do the work, I relied on the statements I was given on StackOverflow, let's see whether we can clarify this further there).

Okay, the people on StackOverflow didn't realize I was specifically asking about :through, it seems. I have updated my question accordingly.