2

How do I test this ActiveRecord relation using ShouldaMatchers?

Models

class ViolatorUnitHistory < ActiveRecord::Base
...
  belongs_to :primary_source, class_name: Source, primary_key: :source_1_id, foreign_key: :id
  belongs_to :secondary_source, class_name: Source, primary_key: :source_2_id, foreign_key: :id
  belongs_to :tertiary_source, class_name: Source, primary_key: :source_3_id, foreign_key: :id
...
end  

class Source < ActiveRecord::Base
  has_many :violator_unit_histories
end

Test

describe "relationships" do
  # Can't figure out this relationship
  it { is_expected.to have_many(:violator_unit_histories).class_name('Source').with_primary_key('source_1_id').with_foreign_key('id') }
end

Current Results

Failures:
  1) Source relationships should have many violator_unit_histories class_name => Source
     Failure/Error: it { is_expected.to have_many(:violator_unit_histories).class_name('Source').with_primary_key('source_1_id').with_foreign_key('id') }
       Expected Source to have a has_many association called violator_unit_histories ()
     # ./spec/models/source_spec.rb:17:in `block (3 levels) in <top (required)>'

Previous Results

it { is_expected.to have_many(:violator_unit_histories) }

Failures:
  1) Source relationships should have many violator_unit_histories
     Failure/Error: it { is_expected.to have_many(:violator_unit_histories) }
       Expected Source to have a has_many association called violator_unit_histories (ViolatorUnitHistory does not have a source_id foreign key.)

I saw the answer posted here for the belongs_to side of the test. But I can't seem to figure out he has_many side to these more complex tests.

Community
  • 1
  • 1
ckib16
  • 383
  • 4
  • 8

2 Answers2

4

First, it looks like your primary / foreign key relationships are a bit off here.

Starting with your model definitions:

class ViolatorUnitHistory < ActiveRecord::Base
...
  belongs_to :primary_source, class_name: Source, primary_key: :source_1_id, foreign_key: :id
  belongs_to :secondary_source, class_name: Source, primary_key: :source_2_id, foreign_key: :id
  belongs_to :tertiary_source, class_name: Source, primary_key: :source_3_id, foreign_key: :id
...
end  

class Source < ActiveRecord::Base
  has_many :violator_unit_histories
end

The issue here is that :id doesn't seem to be a foreign key, but a primary key. Conversely, source_1_id appears to be a foreign key defined on the ViolatorUnitHistory model. Is this correct?

In other words, assuming you had an instance of Source with associated violator_unit_histories, what would you expect @source.violator_unit_histories to return? There's nothing in your code that clearly defines what violator_unit_histories actually is.

This is why you're getting this error:

ViolatorUnitHistory does not have a source_id foreign key.

Rails is looking for a source_id foreign key in the ViolatorUnitHistory class, however, one does not exist.

Also, in your tests, you're not explicitly defining what the subject of your tests is. Consider using the expect(<object>).to syntax to make things more clear.

Anthony E
  • 11,072
  • 2
  • 24
  • 44
  • Thanks @anthony-e. Yes, this was a real problem because the data set we were given to work with was not normalized at all. So it made his tricky. My co-worker added some info below that we just figured out. But I'm marking yours as the correct one since it got us on the right path, – ckib16 May 09 '16 at 14:56
1

To piggy back what Anthony E said, the relationship between ViolatorUnitHistory => Source is clearly defined, but the reverse isn't. You can say Violator.first.primary_source, but Source.first.violator_unit_histories isn't correct because of how the database is normalized.

I would probably define each reverse relationship in a similar manner as you did the ViolatorUnitHistory => Source relationship since the ViolatorUnitHistory uses multiple fields to store a Source.id

You could add something like:

class Source < ActiveRecord::Base
...
  # I don't think primary_, secondary_ etc sources is a great name for these, since
  #   since it's really "History that has this as a primary source"
  has_many :primary_sources, class_name: ViolatorUnitHistory, primary_key: :id, foreign_key: :source_1_id
  has_many :secondary_sources, class_name: ViolatorUnitHistory, primary_key: :id, foreign_key: :source_2_id
  has_many :tertiary_sourcs, class_name: ViolatorUnitHistory, primary_key: :id, foreign_key: :source_3_id
...
end 

Then your tests become something like:

describe "relationships" do
  expect(source).to have_many :primary_sources
  expect(source).to have_many :secondary_sources
  expect(source).to have_many :tertiary_sources
end

The alternative would be to normalize your database so you had a join table which included ViolatorUnitHistory.id, Source.id, and some indicator of ordinality (source_order or something, for your 1st, 2nd, 3rd)

Disclosure: I work with Chris and I've seen the actual data source so I have a little more insight into the question

Jay Dorsey
  • 3,563
  • 2
  • 18
  • 24