Подтвердить что ты не робот

FactoryGirl: почему атрибуты пропускают некоторые атрибуты?

Я хочу использовать FactoryGirl.attributes_for для тестирования контроллера, как в:

it "raise error creating a new PremiseGroup for this user" do
  expect {
    post :create, {:premise_group => FactoryGirl.attributes_for(:premise_group)}
  }.to raise_error(CanCan::AccessDenied)
end

... но это не работает, потому что #attributes_for исключает атрибут: user_id. Вот разница между #create и #attributes_for:

>> FactoryGirl.create(:premise_group)
=> #<PremiseGroup id: 3, name: "PremiseGroup_4", user_id: 6, is_visible: false, is_open: false)
>> FactoryGirl.attributes_for(:premise_group)
=> {:name=>"PremiseGroup_5", :is_visible=>false, :is_open=>false}

Обратите внимание, что: user_id отсутствует в #attributes_for. Это ожидаемое поведение?

FWIW, файл моих фабрик включает определения для :premise_group и для :user:

FactoryGirl.define do
  ...
  factory :premise_group do
    sequence(:name) {|n| "PremiseGroup_#{n}"}
    user
    is_visible false
    is_open false
  end
  factory :user do
    ...
  end
end
4b9b3361

Ответ 1

Копаем глубоко в документацию FactoryGirl, например. эта страница wiki, вы найдете упоминания о том, что attributes_for игнорирует ассоциации. Наше дело не в том, чтобы задаться вопросом, почему (но я действительно представил вопрос) (но см. Обновление ниже). В качестве обходного пути я обернул вспомогательный метод вокруг FactoryGirl.build(...).attributes, который разбивает id, created_at и updated_at:

def build_attributes(*args)
  FactoryGirl.build(*args).attributes.delete_if do |k, v| 
    ["id", "created_at", "updated_at"].member?(k)
  end
end

Итак, теперь:

>> build_attributes(:premise_group)
=> {"name"=>"PremiseGroup_21", "user_id"=>29, "is_visible"=>false, "is_open"=>false}

... это именно то, что ожидалось.

Обновление

Взяв комментарии от создателей FactoryGirl, я понимаю, почему attributes_for игнорирует ассоциации: ссылка на ассоциацию генерирует вызов к db, который в некоторых случаях может значительно замедлить тесты. Но если вам нужны ассоциации, подход build_attributes, показанный выше, должен работать.

Ответ 2

Я думаю, что это небольшое улучшение по сравнению с бесстрашным ответом, хотя это зависит от вашего желаемого результата.

Проще всего объяснить с помощью примера. Скажем, у вас есть лат и длинные атрибуты в вашей модели. На вашей форме у вас нет лат и длинных полей, но лат-градус, лат-мин, лат-секунда и т.д. Эти позже могут быть преобразованы в десятичную длинную длинную форму.

Скажите, что ваш factory выглядит так:

factory :something
  lat_d 12
  lat_m 32
  ..
  long_d 23
  long_m 23.2
end

Бесстрашный build_attributes вернет { lat: nil, long: nil}. Пока build_attributes ниже вернет { lat_d: 12, lat_m: 32..., lat: nil...}

def build_attributes
  ba = FactoryGirl.build(*args).attributes.delete_if do |k, v| 
    ["id", "created_at", "updated_at"].member?(k)
  end
  af = FactoryGirl.attributes_for(*args)
  ba.symbolize_keys.merge(af)
end

Ответ 3

Чтобы подробнее разобраться с данным решением build_attributes, я изменил его, чтобы добавить только доступные ассоциации:

def build_attributes(*args)
    obj = FactoryGirl.build(*args)
    associations = obj.class.reflect_on_all_associations(:belongs_to).map { |a| "#{a.name}_id" }
    accessible = obj.class.accessible_attributes

    accessible_associations = obj.attributes.delete_if do |k, v| 
        !associations.member?(k) or !accessible.include?(k)
    end

    FactoryGirl.attributes_for(*args).merge(accessible_associations.symbolize_keys)
end

Ответ 4

Вот еще один способ:

FactoryGirl.build(:car).attributes.except('id', 'created_at', 'updated_at').symbolize_keys

Ограничения:

  • Он не генерирует атрибуты для ассоциаций HMT и HABTM (поскольку эти ассоциации хранятся в таблице соединений, а не фактическом атрибуте).
  • Стратегия ассоциации в factory должна быть create, как в association :user, strategy: :create. Эта стратегия может сделать ваш factory очень медленным, если вы не используете его с умом.

Ответ 5

Принятый ответ кажется устаревшим, так как он не работал для меня, после того, как я покопался в Интернете и особенно в этой проблеме Github, я представляю вам:

Чистая версия для самых основных функциональных возможностей Rails 5+

Это создает ассоциации :belongs_to и добавляет их idtype if :polymorphic) к атрибутам. Он также включает в себя код через FactoryBot::Syntax::Methods вместо собственного модуля, ограниченного контроллерами.

Спецификация/поддержка /factory_bot_macros.rb

module FactoryBot::Syntax::Methods
  def nested_attributes_for(*args)
    attributes = attributes_for(*args)
    klass = args.first.to_s.camelize.constantize

    klass.reflect_on_all_associations(:belongs_to).each do |r|
      association = FactoryBot.create(r.class_name.underscore)
      attributes["#{r.name}_id"] = association.id
      attributes["#{r.name}_type"] = association.class.name if r.options[:polymorphic]
    end

    attributes
  end
end

это адаптированная версия jamesst20 по выпуску github - слава ему 👏