我试图用嵌套的attrs创建一个有效的工厂,但无法弄清楚如何去做。创建之后我无法做到这一点,因为产品创建验证也应该创建嵌套属性。
如何创建有效工厂?
product.rb
has_many :industry_products, dependent: :destroy, inverse_of: :product
has_many :industries, through: :industry_products
has_many :product_features, dependent: :destroy
has_many :product_competitions, dependent: :destroy
has_many :product_usecases, dependent: :destroy
accepts_nested_attributes_for :industry_products, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :product_features, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :product_competitions, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :product_usecases, reject_if: :all_blank, allow_destroy: true
validate :product_industries_limit
validate :product_features_limit
validate :product_competitions_limit
validate :product_usecases_limit
# other 3 validation method (features, competitions, usecases) are the same
def product_industries_limit
if self.industries.reject(&:marked_for_destruction?).count > 5
self.errors.add :base, "You can't choose more than 5 industries."
elsif self.industries.reject(&:marked_for_destruction?).blank?
self.errors.add :base, "You have to choose at least 1 industry."
end
end
工厂
factory :product, class: Product do
name { Faker::Commerce.product_name }
company { Faker::Company.name }
website { 'https://example.com' }
oneliner { Faker::Lorem.sentence }
description { Faker::Lorem.paragraph }
user
trait :product_with_nested_attrs do
after(:create) do |product|
create(:product_competititon, product: product)
create(:product_usecase, product: product)
create(:product_feature, product: product)
end
end
trait :with_children do
ignore do
product_competition { build :product_competition }
product_usecase { build :product_usecase }
product_feature { build :product_feature }
end
after(:build) do |product, evaluator|
if evaluator.product_competitions.present?
product.product_competitions = evaluator.product_competitions
else
product.product_competitions << evaluator.product_competition
end
if evaluator.product_usecases.present?
product.product_usecases = evaluator.product_usecases
else
product.product_usecases << evaluator.product_usecase
end
if evaluator.product_features.present?
product.product_features = evaluator.product_features
else
product.product_features << evaluator.product_feature
end
end
end
trait :with_product_all do
ignore do
product_competititon { build :product_competititon }
product_usecase { build :product_usecase }
product_feature { build :product_feature }
end
after(:build) do |product, evaluator|
# If you have to skip validation, add the following line.
product.class.skip_callback(:save, :after, :product_competitions_limit)
product.product_competitions = evaluator.product_competitions
product.class.skip_callback(:save, :after, :product_features_limit)
product.product_features = evaluator.product_features
product.class.skip_callback(:save, :after, :product_usecases_limit)
product.product_usecases = evaluator.product_usecases
end
end
product_spec
let(:product) { FactoryGirl.create(:product) }
let(:test_product) {create(:product, :product_with_nested_attrs) }
let
let(:product_with_attrs) { create(:product) do |product|
product.product_features.create(attributes_for(:product_feature))
product.product_competitions.create(attributes_for(:product_competition))
product.product_usecases.create(attributes_for(:product_usecase))
end }
let(:new_product) { create(:product, :with_children) }
let(:new_product_2) { create(:product, :with_product_all) }
#this throws NoMethodError: undefined method `with_indifferent_access' for #<String:0x007fa4ff6ad2d0>
it "has a valid factory" do
attrs = attributes_for(:product).merge({
user_id: user.id,
product_features_attributes: attributes_for(:product_feature),
product_usecases_attributes: attributes_for(:product_usecase),
product_competitions_attributes: attributes_for(:product_competition),
product_industry: attributes_for(:industry)
})
expect(Product.new(attrs)).to be_valid
end
#this throws: ActiveRecord::RecordInvalid: Validation failed:
#You have to choose at least 1 industry., You must have at least 1 product feature., You must name at least 1 competition., You must describe at least 1 usecase.
it "has a valid factory 3" do
expect(test_product).to be_valid
end
#this throws the same: ActiveRecord::RecordInvalid: Validation failed:
#You have to choose at least 1 industry., You must have at least 1 product feature., You must name at least 1 competition., You must describe at least 1 usecase.
it "has a valid factory 4" do
expect(product_with_attrs).to be_valid
end
更新:
#this throws: ActiveRecord::RecordInvalid: Validation failed:
#You have to choose at least 1 industry., You must have at least 1 product feature., You must name at least 1 competition., You must describe at least 1 usecase.
it "has a valid factory 5" do
expect(new_product).to be_valid
end
#this throws the same: ActiveRecord::RecordInvalid: Validation failed:
#You have to choose at least 1 industry., You must have at least 1 product feature., You must name at least 1 competition., You must describe at least 1 usecase.
it "has a valid factory 6" do
expect(new_product_2).to be_valid
end
#checking puts
it "has a valid factory 7" do
p = build(:product, :with_children)
puts p.product_competitions #gives: #<ProductCompetition:0x007fa4fbdd6fb0>
expect(p).to be_valid
end
答案 0 :(得分:1)
此工厂示例将有助于使用一个 product_competition 创建产品。拥有每种关系的特征将使工厂更具组合性。您可以使用工厂中的 skip_callback 跳过验证(如果必须)。
trait :with_product_competititon do
ignore do
product_competititon { build :product_competititon }
end
after(:build) do |product, evaluator|
# If you have to skip validation, add the following line.
product.class.skip_callback(:save, :after, :product_competitions_limit)
product.product_competitions = [evaluator.product_competitions]
end
end
如果你不关心构建对象的特性并希望有一家工厂去做所有事情,你可以试试这个
trait :with_children do
ignore do
product_competititon { build :product_competititon }
product_usecases { build :product_usecases }
product_features { build :product_features }
end
after(:build) do |product, evaluator|
if evaluator.product_competititons.present?
product.product_competititons = evaluator.product_competititons
else
product.product_competititons << evaluator.product_competititon
end
if evaluator.product_usecases.present?
product.product_usecases = evaluator.product_usecases
else
product.product_usecases << evaluator.product_usecase
end
if evaluator.product_features.present?
product.product_features = evaluator.product_features
else
product.product_features << evaluator.product_feature
end
end
PS:我还没有测试过这段代码。