【问题标题】:How do I set an _id value in FactoryBot that isn't a foreign key?如何在 FactoryBot 中设置不是外键的 _id 值?
【发布时间】:2018-01-10 02:45:08
【问题描述】:

我正在使用 FactoryBot(以前的 FactoryGirl)为我的测试创建一些工厂数据。我有一个通过模式看起来像这样的模型(只剩下相关的东西):

create_table "items", force: :cascade do |t|
    t.text "team"
    t.text "feature_id"
    t.text "feature"
    t.timestamps
end

但是,feature_idfeature 不是对功能对象的引用...它们只是字符串。

我这样定义我的工厂:

FactoryBot.define do
  factory :item do
    team "TheTeam"
    sequence(:feature_id) {|n| "Feature#{n}" }
    feature { Faker::Lorem.sentence }
  end
end

简单的案例有效:

> FactoryBot.create(:item)
=> #<Item:0x007fad8cbfc048
 id: 1,
 team: "TheTeam",
 feature_id: "Feature1",
 feature: "Qui voluptatem animi et rerum et.",
 created_at: Wed, 10 Jan 2018 02:40:01 UTC +00:00,
 updated_at: Wed, 10 Jan 2018 02:40:01 UTC +00:00>

但是当我想指定自己的 feature_id 时,会发生这种情况:

> FactoryBot.create(:item, feature_id: "123")
=> #<Item:0x007fad8d30a880
 id: 2,
 team: "TheTeam",
 feature_id: "123",
 feature: nil,
 created_at: Wed, 10 Jan 2018 02:40:59 UTC +00:00,
 updated_at: Wed, 10 Jan 2018 02:40:59 UTC +00:00>

您可以看到feature 现在是nil。我假设这是因为它试图推断 feature_idfeature 在某种程度上是相关的。但在这种情况下,我不希望他们成为。

有没有更好的方法来定义工厂,以便将它们视为不相关的字段?

顺便说一句,如果我尝试同时设置 feature_idfeature 它看起来像这样:

> FactoryBot.create(:item, feature_id: "123", feature: "hi")
=> #<Item:0x007fad8d262810
 id: 3,
 team: "TheTeam",
 feature_id: nil,
 feature: nil,
 created_at: Wed, 10 Jan 2018 02:45:01 UTC +00:00,
 updated_at: Wed, 10 Jan 2018 02:45:01 UTC +00:00>

所以它只是将两个字段都设置为nil。我怀疑 FactoryBot 正试图根据它们的名称对这些字段“聪明”。我会更改它们,但它们已经在 Db 中设置。

【问题讨论】:

  • 如果您在 FactoryBot 之外创建记录,此模型是否按预期工作?例如,如果你做Item.create(team: 'TheTeam', feature_id: '123', feature: 'Description of feature'),你会得到一个保存所有属性的记录吗?
  • 好问题,@moveson。是的,它通过控制台工作,只是做你在那里做的事情。我没有对字段进行任何验证,也没有任何关系(此时)。 Item 类不引用任何其他内容。所以 Rails 本身就很好。它似乎只是 FactoryBot 中的东西

标签: ruby-on-rails ruby rspec factory-bot factory


【解决方案1】:

FactoryBot 似乎确实在做出假设,但我还没有找到改变这些假设的方法。可能值得打开一个问题来看看维护者必须提供什么。

同时,这里有一个解决方法:

FactoryBot.define do
  FEATURE_IDS ||= (1..1000).cycle

  factory :item do
    team "TheTeam"

    transient { without_feature_id false }
    transient { without_feature false }

    after(:build, :stub) do |item, evaluator|
      item.feature_id = "Feature#{FEATURE_IDS.next}" unless evaluator.without_feature_id
      item.feature = Faker::Lorem.sentence unless evaluator.without_feature
    end
  end
end

这将在上述情况下正常运行。

递增是棘手的。我无法找到在资源构造上下文之外使用 FactoryBot 序列的方法,因此我使用 Enumerator 并调用 #next 来创建序列。这与 FactoryBot 序列的工作方式类似,只是在测试运行过程中无法重置为 1。

RSpec 测试证明它按预期工作,无论我们是在数据库中创建项目还是在内存中构建它们:

context 'when more than one item is created' do
  let(:item_1) { create(:item) }
  let(:item_2) { create(:item) }

  it 'increments feature_id by 1' do
    expect(item_1.feature_id).to be_present
    expect(item_2.feature_id).to eq(item_1.feature_id.next)
  end
end

context 'when using build instead of create' do
  let(:item_1) { build(:item) }
  let(:item_2) { build(:item) }

  it 'increments feature_id by 1' do
    expect(item_1.feature_id).to be_present
    expect(item_2.feature_id).to eq(item_1.feature_id.next)
  end
end

请注意,您不能使用典型构造创建没有 feature_id 或特征的项目;例如:

>> item = create(:item, feature_id: nil)

会导致

>> item.feature_id
#> "Feature1"

如果你想创建一个没有 feature 和 feature_id 字段的对象,你可以这样做:

create(:item, without_feature_id: true, without_feature: true)

【讨论】:

  • 这看起来不错,尽管有没有办法在after() 块中仍然获得feature_idsequence 部分?这是一个狡辩,但最好让feature_id 是一个顺序递增的Feature1Feature2 等,如sequence 提供
  • 我非常喜欢这个!不过,我认为不是提供一个常数,而是:item.feature_id = "Feature#{item.class.count + 1}" unless evaluator.without_feature_id 然后将项目数加 1。所以它不会重置,否则应该可以工作。它确实为每次创建添加了额外的数据库调用,但至少目前这不是问题。
  • 请记住,您可能不想每次都在数据库中创建项目。通常(希望非常频繁)您会希望只使用buildbuild_stubbed 在内存中构建项目。出于这个原因,我会避免依赖数据库的增量值。也就是说,我错误地说明了这个增量变量的工作方式。实际上,它将在测试运行开始时重置,而不是在每个单元测试开始时重置,这可能是您无论如何都想要的。
  • 啊……好电话。是的,让它依赖于数据库计数在这方面会有问题。谢谢!
  • 一个有趣的事情要注意:如果我将属性更改为feature_idfeature_title,一切都在原始设置中正常工作。所以这肯定是因为 X_idX 命名模式不知何故。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-12
  • 1970-01-01
  • 1970-01-01
  • 2012-09-11
  • 2018-12-01
  • 1970-01-01
相关资源
最近更新 更多