与FactoryGirl,亲子协会的Rails。省略在子模型中创建一个记录

时间:2017-01-21 19:34:36

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

我有两个型号。 父模型Tag

class Tag < ApplicationRecord
  has_many :keywords, inverse_of: :tag, dependent: :destroy
  accepts_nested_attributes_for :keywords

  validates :keywords, presence: true
end

正如您所看到的,tag应该至少有一个keyword

儿童模型Keyword

class Keyword < ApplicationRecord
  belongs_to :tag, inverse_of: :keywords

  validates :tag, presence: true
end

以下是FactoryGirl工厂的代码 tag工厂:

FactoryGirl.define do
  factory :tag do
    sequence(:name) { |n| "Tag#{n}" }
    after(:build) do |tag_object|
      tag_object.keywords << build(:keyword, tag: tag_object)
    end
  end
end

keyword工厂:

FactoryGirl.define do
  factory :keyword do
    tag
    sequence(:name) { |n| "Keyword#{n}" }
  end
end

当我在带有keywords工厂的keyword表中创建新记录时,它会在keywords表中再创建一条记录,该记录与tags表中的同一父记录相关联

如何省略在keywords表中再创建一条记录并保持工厂有效?

irb(main):023:0> FactoryGirl.create :keyword
   (0.1ms)  BEGIN
  Keyword Exists (0.7ms)  SELECT  1 AS one FROM "keywords" WHERE "keywords"."name" = $1 LIMIT $2  [["name", "Keyword1"], ["LIMIT", 1]]
  Tag Exists (0.3ms)  SELECT  1 AS one FROM "tags" WHERE "tags"."name" = $1 LIMIT $2  [["name", "Tag1"], ["LIMIT", 1]]
  SQL (0.5ms)  INSERT INTO "tags" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id"  [["name", "Tag1"], ["created_at", 2017-01-21 19:20:14 UTC], ["updated_at", 2017-01-21 19:20:14 UTC]]
  SQL (0.6ms)  INSERT INTO "keywords" ("tag_id", "name", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["tag_id", 36], ["name", "Keyword1"], ["created_at", 2017-01-21 19:20:14 UTC], ["updated_at", 2017-01-21 19:20:14 UTC]]
   (10.4ms)  COMMIT
   (0.1ms)  BEGIN
  Keyword Exists (0.4ms)  SELECT  1 AS one FROM "keywords" WHERE "keywords"."name" = $1 LIMIT $2  [["name", "Keyword2"], ["LIMIT", 1]]
  SQL (0.4ms)  INSERT INTO "keywords" ("tag_id", "name", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["tag_id", 36], ["name", "Keyword2"], ["created_at", 2017-01-21 19:20:14 UTC], ["updated_at", 2017-01-21 19:20:14 UTC]]
   (4.4ms)  COMMIT
=> #<Keyword id: 63, tag_id: 36, name: "Keyword2", created_at: "2017-01-21 19:20:14", updated_at: "2017-01-21 19:20:14">
irb(main):024:0> 

您可以看到它在tags中创建了一条记录,在keywords表中创建了一条记录,之后又在keywords表中创建了一条记录。

2 个答案:

答案 0 :(得分:1)

FactoryGirl在构建过程中为模型创建所有声明的关联。这意味着FactoryGirl.build :keyword会执行FactoryGirl.create :tag,因此Keyword#tag_id会有Keyword的ID来帮助传递irb(main):023:0> FactoryGirl.create :keyword ### keywordA = Keyword.new ### call create(:tag) because of association ### tag1 = Tag.new ### call build(:keyword) in after(:build) ###.keywordB.new(tag: tag1) # which prevents trying to make a new tag! ### tag1.save # which saves the keywordB (0.1ms) BEGIN Keyword Exists (0.7ms) SELECT 1 AS one FROM "keywords" WHERE "keywords"."name" = $1 LIMIT $2 [["name", "Keyword1"], ["LIMIT", 1]] Tag Exists (0.3ms) SELECT 1 AS one FROM "tags" WHERE "tags"."name" = $1 LIMIT $2 [["name", "Tag1"], ["LIMIT", 1]] SQL (0.5ms) INSERT INTO "tags" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["name", "Tag1"], ["created_at", 2017-01-21 19:20:14 UTC], ["updated_at", 2017-01-21 19:20:14 UTC]] SQL (0.6ms) INSERT INTO "keywords" ("tag_id", "name", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["tag_id", 36], ["name", "Keyword1"], ["created_at", 2017-01-21 19:20:14 UTC], ["updated_at", 2017-01-21 19:20:14 UTC]] (10.4ms) COMMIT ### keywordA.tag = tag1 ### keywordA.save (0.1ms) BEGIN Keyword Exists (0.4ms) SELECT 1 AS one FROM "keywords" WHERE "keywords"."name" = $1 LIMIT $2 [["name", "Keyword2"], ["LIMIT", 1]] SQL (0.4ms) INSERT INTO "keywords" ("tag_id", "name", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["tag_id", 36], ["name", "Keyword2"], ["created_at", 2017-01-21 19:20:14 UTC], ["updated_at", 2017-01-21 19:20:14 UTC]] (4.4ms) COMMIT ### Since keywordA gets saved after keywordB, ### keywordB gets a 1 from the sequence and ### keywordA gets a 2 from the sequence => #<Keyword id: 63, tag_id: 36, name: "Keyword2", created_at: "2017-01-21 19:20:14", updated_at: "2017-01-21 19:20:14"> irb(main):024:0> 模型上的验证。

这与您看到的数据库活动一致。

keyword

这只是所发生事情的要点。就个人而言,我无法想象基于数据库的架构想要tag没有它create(:tag)所以我只需要调用keyword并获得前面提到的第一个FactoryGirl.define do factory :tag do sequence(:name) { |n| "Tag#{n}" } after(:build) do |this| this.keywords << build(:keyword) if this.keywords.empty? end end end FactoryGirl.define do factory :keyword do sequence(:name) { |n| "Keyword#{n}" } after(:build) do |this| this.tag ||= build(:tag) end end end build(:tag) # unsaved build(:tag).keyword # also unsaved create(:tag) # saved create(:tag).keyword # also saved build(:keyword) # unsaved build(:keyword).tag # also unsaved create(:keyword) # saved create(:keyword).tag # also saved # And it still lets you be specific create(:tag, keywords: [create(:keyword, name: "More of a phrase")]) create(:keyword, tag: create(:tag, name: "Pop Me!")) 。但是模式很简单,因此在我们想要测试的100%的情况中,以下内容应该成立:

# Fake the association
FactoryGirl.define do
  factory :keyword do
    sequence(:name) { |n| "Keyword#{n}" }
    tag_id 1 # Danger!
             # Will make it pass validation but you
             # will forget and #tag will not be found
             # or not what you expect
  end
end

# use a trait
FactoryGirl.define do
  factory :keyword do
    sequence(:name) { |n| "Keyword#{n}" }
    trait :with_tag do
      tag
    end
  end
end

# make a new factory
FactoryGirl.define do
  # do not need parent if inside the "factory :tag do"
  factory :tag_with_keyword, parent: :tag do 
    sequence(:name) { |n| "Tag#{n}" }
    keyword
  end
end
# but now we are back to it creating the keyword on build(:tag)

还需考虑其他几个选项:

FactoryGirl

virtualenv确实为你提供了解决许多情况的足够选择,但诀窍在于理解它如何设置关联并试图远离设置隐式循环。

答案 1 :(得分:0)

关键字和标签不能彼此独立存在。您的标签工厂每次调用时都会创建一个关键字,因此您应该调用标签工厂。试试这个:

tag = FactoryGirl.create(:tag)
keyword = tag.keywords.first