我想在控制器测试中使用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
答案 0 :(得分:26)
深入研究FactoryGirl文档,例如this wiki page,您会发现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
方法应该有效。
答案 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
限制:
create
,与association :user, strategy: :create
中一样。如果您不明智地使用它,这种策略可能会使您的工厂变得非常慢。答案 4 :(得分:0)
接受的答案似乎过时了,因为它对我不起作用,尤其是在浏览了this Github issue之后,我向您介绍了
此创建关联,并将其: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-对他表示敬意?