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。换句话说:blogpost需要构建,它们需要关联,并且它们需要同时保存以便验证通过。

这是 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_tohas_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)

并且:bloguser 都关联到 postuser 关联到 post,并且全部同时保存。