Rails 与FactoryGirl,亲子协会。省略在子模型中再创建一条记录
Rails with FactoryGirl, parent-child association. Omit creating one more record in child model
我有两个模型。
父模型 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
当我使用 keyword
工厂在 keywords
table 中创建一条新记录时,它会在 keywords
table 中创建一条与tags
table.
中的相同父记录
如何省略在 keywords
table 中再创建一条记录并保持工厂有效?
irb(main):023:0> FactoryGirl.create :keyword
(0.1ms) BEGIN
Keyword Exists (0.7ms) SELECT 1 AS one FROM "keywords" WHERE "keywords"."name" = LIMIT [["name", "Keyword1"], ["LIMIT", 1]]
Tag Exists (0.3ms) SELECT 1 AS one FROM "tags" WHERE "tags"."name" = LIMIT [["name", "Tag1"], ["LIMIT", 1]]
SQL (0.5ms) INSERT INTO "tags" ("name", "created_at", "updated_at") VALUES (, , ) 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 (, , , ) 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" = LIMIT [["name", "Keyword2"], ["LIMIT", 1]]
SQL (0.4ms) INSERT INTO "keywords" ("tag_id", "name", "created_at", "updated_at") VALUES (, , , ) 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
中创建了一条记录 table,然后在 keywords
中又创建了一条记录 table.
关键字和标签不能相互独立存在。您的标签工厂每次调用时都会创建一个关键字,因此您应该调用标签工厂。试试这个:
tag = FactoryGirl.create(:tag)
keyword = tag.keywords.first
FactoryGirl
在构建过程中为模型创建所有声明的关联。这意味着 FactoryGirl.build :keyword
将执行 FactoryGirl.create :tag
,因此它将具有 Keyword#tag_id
的 ID,以帮助通过 Keyword
模型的验证。
这与您看到的数据库 activity 一致。
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" = LIMIT [["name", "Keyword1"], ["LIMIT", 1]]
Tag Exists (0.3ms) SELECT 1 AS one FROM "tags" WHERE "tags"."name" = LIMIT [["name", "Tag1"], ["LIMIT", 1]]
SQL (0.5ms) INSERT INTO "tags" ("name", "created_at", "updated_at") VALUES (, , ) 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 (, , , ) 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" = LIMIT [["name", "Keyword2"], ["LIMIT", 1]]
SQL (0.4ms) INSERT INTO "keywords" ("tag_id", "name", "created_at", "updated_at") VALUES (, , , ) 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>
这只是发生的事情的要点。就个人而言,我无法想象在没有基于数据库架构的 tag
的情况下需要一个 keyword
,所以我会调用 create(:tag)
并获得第一个 keyword
,如前所述。但模式足够简单,因此以下内容应该在我们想要测试的 100% 情况下成立:
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!"))
还有几个选项需要考虑:
# 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
确实为您提供了足够的选择来解决许多情况,但诀窍是了解它如何设置关联并尽量避免设置隐式循环关联。
我有两个模型。
父模型 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
当我使用 keyword
工厂在 keywords
table 中创建一条新记录时,它会在 keywords
table 中创建一条与tags
table.
如何省略在 keywords
table 中再创建一条记录并保持工厂有效?
irb(main):023:0> FactoryGirl.create :keyword
(0.1ms) BEGIN
Keyword Exists (0.7ms) SELECT 1 AS one FROM "keywords" WHERE "keywords"."name" = LIMIT [["name", "Keyword1"], ["LIMIT", 1]]
Tag Exists (0.3ms) SELECT 1 AS one FROM "tags" WHERE "tags"."name" = LIMIT [["name", "Tag1"], ["LIMIT", 1]]
SQL (0.5ms) INSERT INTO "tags" ("name", "created_at", "updated_at") VALUES (, , ) 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 (, , , ) 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" = LIMIT [["name", "Keyword2"], ["LIMIT", 1]]
SQL (0.4ms) INSERT INTO "keywords" ("tag_id", "name", "created_at", "updated_at") VALUES (, , , ) 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
中创建了一条记录 table,然后在 keywords
中又创建了一条记录 table.
关键字和标签不能相互独立存在。您的标签工厂每次调用时都会创建一个关键字,因此您应该调用标签工厂。试试这个:
tag = FactoryGirl.create(:tag)
keyword = tag.keywords.first
FactoryGirl
在构建过程中为模型创建所有声明的关联。这意味着 FactoryGirl.build :keyword
将执行 FactoryGirl.create :tag
,因此它将具有 Keyword#tag_id
的 ID,以帮助通过 Keyword
模型的验证。
这与您看到的数据库 activity 一致。
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" = LIMIT [["name", "Keyword1"], ["LIMIT", 1]]
Tag Exists (0.3ms) SELECT 1 AS one FROM "tags" WHERE "tags"."name" = LIMIT [["name", "Tag1"], ["LIMIT", 1]]
SQL (0.5ms) INSERT INTO "tags" ("name", "created_at", "updated_at") VALUES (, , ) 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 (, , , ) 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" = LIMIT [["name", "Keyword2"], ["LIMIT", 1]]
SQL (0.4ms) INSERT INTO "keywords" ("tag_id", "name", "created_at", "updated_at") VALUES (, , , ) 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>
这只是发生的事情的要点。就个人而言,我无法想象在没有基于数据库架构的 tag
的情况下需要一个 keyword
,所以我会调用 create(:tag)
并获得第一个 keyword
,如前所述。但模式足够简单,因此以下内容应该在我们想要测试的 100% 情况下成立:
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!"))
还有几个选项需要考虑:
# 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
确实为您提供了足够的选择来解决许多情况,但诀窍是了解它如何设置关联并尽量避免设置隐式循环关联。