嵌套attr与工厂创建

时间:2016-04-22 08:00:22

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

我试图用嵌套的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

1 个答案:

答案 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:我还没有测试过这段代码。

相关问题