FactoryGirl:为什么attributes_for省略了一些属性?

时间:2012-04-24 00:42:17

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

我想在控制器测试中使用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

5 个答案:

答案 0 :(得分:26)

深入研究FactoryGirl文档,例如this wiki page,您会发现attributes_for忽略了关联。我们不想知道为什么(但我确实提交了一个问题)(但请参阅下面的更新)。作为一种解决方法,我在FactoryGirl.build(...).attributes周围包含了一个帮助方法,用于删除idcreated_atupdated_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方法应该有效。

答案 1 :(得分:2)

我认为这比fearless_fool的回答略有改进,尽管这取决于你想要的结果。

最简单的解释一个例子。假设您的模型中有lat和long属性。在您的表单上,您没有lat和long字段,而是lat degree,lat minute,lat second等。这些稍后可以转换为decimal lat long格式。

说你的工厂是这样的:

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

答案 2 :(得分:0)

为了进一步详细说明给定的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

答案 3 :(得分:0)

这是另一种方式:

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

限制:

  • 它不会为HMT和HABTM关联生成属性(因为这些关联存储在连接表中,而不是实际属性中)。
  • 工厂中的关联策略必须为create,与association :user, strategy: :create中一样。如果您不明智地使用它,这种策略可能会使您的工厂变得非常慢。

答案 4 :(得分:0)

接受的答案似乎过时了,因为它对我不起作用,尤其是在浏览了this Github issue之后,我向您介绍了

Rails 5+最基本功能的干净版本

创建关联,并将其:belongs_to(如果为id,则将其type添加到属性中。它还包括通过:polymorphic instead of an own module limited to controllers进行的代码。

spec / support / factory_bot_macros.rb

FactoryBot::Syntax::Methods

这是改编版of jamesst20 on the github issue-对他表示敬意?