应该 have_many 与 class_name、foreign_key 和 primary_key

Shoulda have_many with class_name, foreign_key and primary_key

如何使用 ShouldaMatchers 测试这个 ActiveRecord 关系?

型号

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

测试

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

当前结果

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)>'

以前的结果

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

我看到 here 测试 belongs_to 方面的答案。但我似乎无法理解他 has_many 对这些更复杂测试的看法。

首先,您的主键/外键关系看起来有点不对劲。

从您的模型定义开始:

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

这里的问题是 :id 似乎不是外键,而是主键。相反,source_1_id 似乎是在 ViolatorUnitHistory 模型上定义的外键。这是正确的吗?

换句话说,假设您有一个 Source 的实例与关联的 violator_unit_histories,您期望 @source.violator_unit_histories 到 return 是什么?您的代码中没有任何内容可以清楚地定义 violator_unit_histories 实际上是什么。

这就是您收到此错误的原因:

ViolatorUnitHistory does not have a source_id foreign key.

Rails 正在 ViolatorUnitHistory class 中寻找 source_id 外键,但是不存在。

此外,在您的测试中,您没有明确定义测试的 subject 是什么。考虑使用 expect(<object>).to 语法使事情更清楚。

借用 Anthony E 所说的,ViolatorUnitHistory => Source 之间的关系已明确定义,但反之则不然。您可以说 Violator.first.primary_source,但 Source.first.violator_unit_histories 不正确,因为数据库是如何规范化的。

我可能会以与 ViolatorUnitHistory => Source 关系类似的方式定义每个反向关系,因为 ViolatorUnitHistory 使用多个字段来存储 Source.id

您可以添加如下内容:

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 

然后你的测试变成这样:

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

另一种方法是规范化您的数据库,这样您就有了一个连接 table,其中包括 ViolatorUnitHistory.idSource.id 和一些序数指示符(source_order 或其他东西, 对于你的第一个、第二个、第三个)

披露:我和 Chris 一起工作,我看过实际的数据源,所以我对这个问题有了更多的了解