factory_girl / factory_bot 没有 after(:create) 的深层嵌套关联
factory_girl / factory_bot deeply nested associations without after(:create)
型号:
class User < ApplicationRecord
has_many :blogs
end
class Blog < ApplicationRecord
belongs_to :user
has_many :posts
validates_presence_of :user_id
# a blog record cannot be created without at least one associated post record
validates :posts, :length => { :minimum => 1 }
end
class Post < ApplicationRecord
belongs_to :blog
belongs_to :user
validates_presence_of :user_id, :blog_id
end
验证是我的工厂遇到困难的原因。请注意,除非至少有一个 post
,否则无法创建 blog
。另请注意,除非有 blog_id
,否则无法创建 post
。换句话说:blog
和post
需要构建,它们需要关联,并且它们需要同时保存以便验证通过。
这是 Rails 5,所以我确实对 application.rb
进行了调整,这样 belongs_to
关联就不会在我的工厂中造成太多麻烦:
# config/application.rb
module MyApp
class Application < Rails::Application
Rails.application.config.active_record.belongs_to_required_by_default = false
end
end
工厂:
FactoryGirl.define do
factory :user do
end
end
FactoryGirl.define do
factory :blog do
user
factory :blog_with_post do
after(:create) do |blog, eval|
the_user_of_blog = blog.user
create(:post, blog: blog, user: the_user_of_blog)
end
end
end
end
FactoryGirl.define do
factory :post do
blog
user
end
end
我在测试中所做的是创建一个 user
记录,然后创建一个 blog
记录和一个 post
记录,两者都与同一个 user
。
使用上面的代码:这有效:
@user = create(:user)
create(:blog_with_post, user: @user)
# => ActiveRecord::RecordInvalid: Validation failed: User can't be blank, Posts is too short (minimum is 1 character)
尝试次数
我试过了 after(:build)
:
factory :blog_with_post do
after(:build) do |blog, eval|
blog_user = blog.user
create(:post, blog: blog, user: blog_user)
end
end
# @user = create(:user)
# create(:blog_with_post, user: @user)
# => ActiveRecord::RecordInvalid: Validation failed: Blog can't be blank
我也试过 before(:create)
导致了同样的错误:
factory :blog_with_post do
before(:create) do |blog, eval|
blog_user = blog.user
create(:post, blog: blog, user: blog_user)
end
end
# @user = create(:user)
# create(:blog_with_post, user: @user)
# => ActiveRecord::RecordInvalid: Validation failed: Blog can't be blank
我也试过这个:
factory :blog_with_post do
after(:build) do |blog, eval|
blog_user = blog.user
build(:post, blog: blog, user: blog_user)
end
end
# @user = create(:user)
# create(:blog_with_post, user: @user)
# => ActiveRecord::RecordInvalid: Validation failed: Posts is too short (minimum is 1 character)
以下似乎很接近,但我不知道如何引用与此博客关联的用户:
FactoryGirl.define do
factory :blog do
user
factory :blog_with_post do
posts {build_list :post, 1, user: THE_USER_OF_THIS_BLOG}
end
end
end
这是我得到的最接近的结果,它排除了没有至少一个 post
就无法创建 blog
的验证。我无法弄清楚(对于工厂,直接 rails 我可以做到)。但是,我确实弄清楚了如何让 before(:create)
工作而不是使用 after(:create)
.
最终:归结为对关联在保存之前如何相互了解以及关联如何自然保存的误解。
detailed association reference rails 文档对我帮助很大,结合使用关联构建记录,保存其中一条记录,然后观察它们是如何同时保存的在模型上指定的 belongs_to
或 has_many
关联。
根据这些知识:Post
模型的这两个验证是导致大多数问题的原因:
validates_presence_of :user_id, :blog_id
这个问题(关于这个问题中的代码)是 blog_id
将不存在,当关联 建立 但还没有 saved,所以在某些情况下对工厂无效。
所以我们真的不想确认外键存在(例如:blog_id
)。相反,我们想要验证 关联 是否存在。换句话说:这只是对 presence validation 的误用。所以将验证更改为:
validates_presence_of :user, :blog
现在剩下要做的就是以正确的方式编写工厂:
factory :blog_with_post do
before(:create) do |blog, eval|
create(:post, blog: blog, user: blog.user)
end
end
用法
@user = create(:user)
create(:blog_with_post, user: @user)
并且:blog
和 user
都关联到 post
,user
关联到 post
,并且全部同时保存。
型号:
class User < ApplicationRecord
has_many :blogs
end
class Blog < ApplicationRecord
belongs_to :user
has_many :posts
validates_presence_of :user_id
# a blog record cannot be created without at least one associated post record
validates :posts, :length => { :minimum => 1 }
end
class Post < ApplicationRecord
belongs_to :blog
belongs_to :user
validates_presence_of :user_id, :blog_id
end
验证是我的工厂遇到困难的原因。请注意,除非至少有一个 post
,否则无法创建 blog
。另请注意,除非有 blog_id
,否则无法创建 post
。换句话说:blog
和post
需要构建,它们需要关联,并且它们需要同时保存以便验证通过。
这是 Rails 5,所以我确实对 application.rb
进行了调整,这样 belongs_to
关联就不会在我的工厂中造成太多麻烦:
# config/application.rb
module MyApp
class Application < Rails::Application
Rails.application.config.active_record.belongs_to_required_by_default = false
end
end
工厂:
FactoryGirl.define do
factory :user do
end
end
FactoryGirl.define do
factory :blog do
user
factory :blog_with_post do
after(:create) do |blog, eval|
the_user_of_blog = blog.user
create(:post, blog: blog, user: the_user_of_blog)
end
end
end
end
FactoryGirl.define do
factory :post do
blog
user
end
end
我在测试中所做的是创建一个 user
记录,然后创建一个 blog
记录和一个 post
记录,两者都与同一个 user
。
使用上面的代码:这有效:
@user = create(:user)
create(:blog_with_post, user: @user)
# => ActiveRecord::RecordInvalid: Validation failed: User can't be blank, Posts is too short (minimum is 1 character)
尝试次数
我试过了 after(:build)
:
factory :blog_with_post do
after(:build) do |blog, eval|
blog_user = blog.user
create(:post, blog: blog, user: blog_user)
end
end
# @user = create(:user)
# create(:blog_with_post, user: @user)
# => ActiveRecord::RecordInvalid: Validation failed: Blog can't be blank
我也试过 before(:create)
导致了同样的错误:
factory :blog_with_post do
before(:create) do |blog, eval|
blog_user = blog.user
create(:post, blog: blog, user: blog_user)
end
end
# @user = create(:user)
# create(:blog_with_post, user: @user)
# => ActiveRecord::RecordInvalid: Validation failed: Blog can't be blank
我也试过这个:
factory :blog_with_post do
after(:build) do |blog, eval|
blog_user = blog.user
build(:post, blog: blog, user: blog_user)
end
end
# @user = create(:user)
# create(:blog_with_post, user: @user)
# => ActiveRecord::RecordInvalid: Validation failed: Posts is too short (minimum is 1 character)
以下似乎很接近,但我不知道如何引用与此博客关联的用户:
FactoryGirl.define do
factory :blog do
user
factory :blog_with_post do
posts {build_list :post, 1, user: THE_USER_OF_THIS_BLOG}
end
end
end
这是我得到的最接近的结果,它排除了没有至少一个 post
就无法创建 blog
的验证。我无法弄清楚(对于工厂,直接 rails 我可以做到)。但是,我确实弄清楚了如何让 before(:create)
工作而不是使用 after(:create)
.
最终:归结为对关联在保存之前如何相互了解以及关联如何自然保存的误解。
detailed association reference rails 文档对我帮助很大,结合使用关联构建记录,保存其中一条记录,然后观察它们是如何同时保存的在模型上指定的 belongs_to
或 has_many
关联。
根据这些知识:Post
模型的这两个验证是导致大多数问题的原因:
validates_presence_of :user_id, :blog_id
这个问题(关于这个问题中的代码)是 blog_id
将不存在,当关联 建立 但还没有 saved,所以在某些情况下对工厂无效。
所以我们真的不想确认外键存在(例如:blog_id
)。相反,我们想要验证 关联 是否存在。换句话说:这只是对 presence validation 的误用。所以将验证更改为:
validates_presence_of :user, :blog
现在剩下要做的就是以正确的方式编写工厂:
factory :blog_with_post do
before(:create) do |blog, eval|
create(:post, blog: blog, user: blog.user)
end
end
用法
@user = create(:user)
create(:blog_with_post, user: @user)
并且:blog
和 user
都关联到 post
,user
关联到 post
,并且全部同时保存。