public
Last active

Should factory_girl bypass :attr_accessible by default?

  • Download Gist
gistfile1.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
# factory_girl has difficulty playing nicely with attr_accessible.
 
class Article
belongs_to :owner
attr_accessible :name
end
 
Factory.define(:article) do |a|
a.association :owner
a.sequence(:name) {|i| "Article #{i}"}
end
 
# A core problem is that attr_accessible denies factory_girl's assignment of those specific attributes.
# It forces us as consumers of the Factory to use the :build strategy and then manually assign those
# attributes which is long-winded and annoying to say the least.
 
@article = Factory.build(:article)
@article.owner = Factory(:owner)
 
# Anyone who's used attr_accessible before will recognize a similar pattern in their controllers.
 
def create
@article = Article.new(params[:article])
@article.owner = admin? ? User.find(params[:article][:owner_id]) : current_user
@article.save
end
 
# For the controller case, we can leverage Ryan Bates' trusted-params plugin.
 
def create
params[:article].trust(:owner_id) if admin?
@article = Article.new(params[:article])
@article.save
end
 
# However, we're still left out to dry if we want to use factory_girl for our functional tests.
# My current solution is to define two separate factories to properly test both an admin and a regular
# user creating an article.
 
Factory.define(:attrs_for_article, :default_strategy => :attributes_for, :class => 'Article') do |a|
a.sequence(:name) {|i| "Article #{i}"}
end
 
# When used in conjunction with the trusted-params snippet, this works as intended.
Factory.define(:article, :parent => :attrs_for_article) do |a|
a.association :owner
end
 
# So great, now I can do my functional tests. But what about my unit tests?
 
Factory(:article) # ==> error that owner is not set!! FUCK!
 
# Grrrr. So how can I get trusted-params-like functionality in my models/unit tests?
 
# One way that factory_girl could solve this would be to support a before_save callback, e.g.
Factory.define(:article, :parent => :attrs_for_article) do |a|
a.before_save {|article| article.owner ||= Factory(:owner) }
end
 
# Another option would be to bake in a :bypass_attr_accessible option for factories, e.g.
 
Factory(:article, :bypass_attr_accessible)
 
# But even this has its shortcomings. Now I as a consumer of the Article factory must remember that it's
# protected some attributes (which I'm not even passing in because they're part of the default factory
# definition).
 
# Perhaps a better solution is to add it as an option to the factory definition?
 
Factory.define(:article, :attr_accessible => :bypass) do |a|
a.association :owner
a.sequence(:name) {|i| "Article #{i}"}
end
 
# What is nice about this is we can very easily test our authorization in our functional tests.
 
def test_should_update_article_as_a_regular_user
@user = Factory(:user)
@name = 'Something now in the factory'
@article = Factory(:article, :name => @name, :owner => @user)
log_in @user
# keep in mind the article factory will create a brand new user!
put :update, :id => @article.id, :article => Factory.attributes_for(:article)
assert_equal @article.reload.owner, @current_user
assert_not_equal @article.name, @name
end
 
# Or better yet, should factory_girl just bypass attr_accessible by default?

Hi John. What did you end up doing to use factory_girl and attr_accessible together? (rails 2.3.5, factory_girl 1.3.2)

Hey Andy, honestly I don't remember. I actually have a sneaking suspicion that this gist is inaccurate...that attr_accessible was not giving me problems.

attr_accessible does seem to be causing the problems in my use of factory_girl for the record... so I wouldn't be surprised if it was your problem, too.

Also, for the poor soul who googled your way here 3 years in the future, it looks like factory_girl callbacks have since solved this, in a manner similar to the before_save suggestion above: http://codeulate.com/2009/11/factory_girl-callbacks/

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.