如何为Factorybot生成的关联修复“ NoMethodError:nil:NilClass的未定义方法'cf_type'”

时间:2019-09-17 07:09:14

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

我收到以下rspec错误,因为关联CustomField为nil,但是它应该由Factorybot生成并且我无法弄清楚为什么不是。项目关联工作正常。

Rspec测试:

it { should belong_to(:custom_field) }
it { should validate_presence_of(:custom_field) }

Rspec错误:

NoMethodError: undefined method `cf_type' for nil:NilClass

  0) CustomFieldValue validations should validate that :custom_field_id cannot be empty/falsy
     Failure/Error: ["single_select", "multi_select", "radiobutton", "checkbox", "label"].include?(custom_field.cf_type)

     NoMethodError:
       undefined method `cf_type' for nil:NilClass
     # ./app/models/custom_field_value.rb:13:in `option_field?'
     # ./spec/models/custom_field_value_spec.rb:18:in `block (3 levels) in <top (required)>'

NoMethodError: undefined method `cf_type' for nil:NilClass

  0) CustomFieldValue validations should belong to custom_field required: true
     Failure/Error: ["single_select", "multi_select", "radiobutton", "checkbox", "label"].include?(custom_field.cf_type)

     NoMethodError:
       undefined method `cf_type' for nil:NilClass
     # ./app/models/custom_field_value.rb:13:in `option_field?'
     # ./spec/models/custom_field_value_spec.rb:20:in `block (3 levels) in <top (required)>'

型号:CustomFieldValue

class CustomFieldValue < ApplicationRecord
belongs_to :custom_field
belongs_to :project

validates :custom_field, :project, presence: :true
validate :options_existence, if: :option_field?
validate :allowed_value_for_option_fields, if: -> cfv { cfv.option_field? && cfv.options_exist? }


# Conditions for validations
def option_field?
["single_select", "multi_select", "radiobutton", "checkbox", "label"].include?(custom_field.cf_type)
end

def options_exist?
  !custom_field.custom_field_options.empty?
end

private

# Custom validations
# Validate that option fields have at least 1 option defined
def options_existence
  if custom_field.custom_field_options.empty?
    errors.add(:base, "No custom field option was found")
  end
end

# Validation that only custom field option id can be stored for custom fields with options defined
def allowed_value_for_option_fields
  if !custom_field.custom_field_options.map{|cfo| cfo.id.to_s}.include?(string_value)
    errors.add(:base, "Custom field option id is the only accepted value")
  end
end

型号:CustomField

class CustomField < ApplicationRecord
  enum cf_type: { string: 10, text: 20, number: 30, single_select: 40,
              multi_select: 50, checkbox: 60, radiobutton: 70, date: 80,
              single_user: 90, multi_user: 100, label: 110 }

  belongs_to :organization
  has_many :custom_field_values, dependent: :destroy
  has_many :custom_field_options, dependent: :destroy

  accepts_nested_attributes_for :custom_field_options

  validates :cf_type, :name, :organization, presence: :true
end

型号:项目

class Project < ApplicationRecord
  belongs_to :organization
  has_many :custom_field_values, dependent: :destroy

  validates :name, :key, :organization, presence: :true
  validates :key, uniqueness: { case_sensitive: false }, length: { minimum: 2, maximum: 25 }

  # Validation of format
  # Valid Input: abcd, xyz, aaa, aa-bb
  # Invalid Input: abC, XYZ, 123, ABC123, -abc, abc-
  validates_format_of :key, :with => /\A(?!-)[a-z][-\w]+(?<!-)\z/

  accepts_nested_attributes_for :custom_field_values
end

RSpec:custom_field_value_spec.rb

  require "rails_helper"

  RSpec.describe CustomFieldValue, :type => :model do

  subject {
    create(:custom_field_value)
  }

  context "validations" do
    it { should validate_presence_of :project }
    it { should validate_presence_of :custom_field }
    it { should belong_to(:project) }
    it { should belong_to(:custom_field) }

    it { expect { create(:custom_field_value, number_value: 1) }.to raise_error(ActiveRecord::RecordInvalid, /Value can be set only for one of the string_value, text_value, number_value or date_value/) }
    it { expect { create(:custom_field_value_for_checkbox) }.to raise_error(ActiveRecord::RecordInvalid, /Custom field option id is the only accepted value/) }

  end
end

工厂:custom_field_values.rb(factorybot)

  FactoryBot.define do
  factory :custom_field_value do
    sequence(:string_value) { |n| "My String#{n}" }
    association :project
    association :custom_field, cf_type: :string
  end

  factory :custom_field_value_for_checkbox, class: CustomFieldValue do
    sequence(:string_value) { |n| "My String#{n}" }
    association :project
    association :custom_field, factory: :custom_field_with_custom_field_options, cf_type: :checkbox
  end
end

工厂:custom_fields.rb(factorybot)

FactoryBot.define do
  factory :custom_field do
    cf_type { :string }
    sequence(:name) { |n| "Name#{n}" }
    sequence(:description) { |n| "Description#{n}" }
    association :organization

    factory :custom_field_with_custom_field_values do
      transient do
        custom_field_values_count { 3 }
      end

      after(:create) do |custom_field, evaluator|
        create_list(:custom_field_value, evaluator.custom_field_values_count, custom_field: custom_field)
      end
    end

    factory :custom_field_with_custom_field_options do
      transient do
        custom_field_options_count { 3 }
      end

      after(:create) do |custom_field, evaluator|
        create_list(:custom_field_option, evaluator.custom_field_options_count, custom_field: custom_field)
      end
    end

    factory :custom_field_with_custom_field_values_and_options do
      transient do
         custom_field_values_and_options_count { 3 }
      end

      after(:create) do |custom_field, evaluator|
        create_list(:custom_field_value, evaluator.custom_field_values_and_options_count, custom_field: custom_field)
        create_list(:custom_field_option, evaluator.custom_field_values_and_options_count, custom_field: custom_field)
      end
    end
  end
end

工厂:projects.rb(factorybot)

FactoryBot.define do
  factory :project do
    sequence(:name) { |n| "Name#{n}" }
    sequence(:key) { |n| random_name }
    association :organization
  end
end

def random_name(length=5)
  source = ('a'..'z').to_a.shuffle.join
  name = ""
  length.times{ name += source[rand(source.size)].to_s }
  return name
end

1 个答案:

答案 0 :(得分:1)

您可以对条件验证进行存根以通过测试:

RSpec.describe CustomFieldValue, :type => :model do
  subject {
    create(:custom_field_value)
  }

  context "validations" do
    before { allow(subject).to receive(:option_field?).and_return(false) }
    it { should validate_presence_of :custom_field }
  end
end