Getting "Can't mass-assign protected attributes:" for nested_attributes - ruby-on-rails

I am on Rails3, I have two model, User, and Post. User has Posts as nested attributes. when I try to save user then I am getting Can't mass-assign protected attributes:.....

Try this attr_accessible in your post model
http://railscasts.com/episodes/26-hackers-love-mass-assignment

if the model definitions are like as follows:
user.rb
class User < ActiveRecord::Base
attr_accessible :name, :posts_attributes
has_many :posts
accepts_nested_attributes_for :posts
end
post.rb
class Post < ActiveRecord::Base
attr_accessible :title, :content :user_id
end
then everything should be fine. You can save user with posts as nested attributes.
Here is a sample codes for the beginners :)
https://github.com/railscash/sample_change_user_role

Mass Assignment is the name Rails gives to the act of constructing your object with a parameters hash. It is "mass assignment" in that you are assigning multiple values to attributes via a single assignment operator.
The following snippets perform mass assignment of the name and topic attribute of the Post model:
Post.new(:name => "John", :topic => "Something")
Post.create(:name => "John", :topic => "Something")
Post.update_attributes(:name => "John", :topic => "Something")
In order for this to work, your model must allow mass assignments for each attribute in the hash you're passing in.
There are two situations in which this will fail:
You have an attr_accessible declaration which does not include :name
You have an attr_protected which does include :name
It recently became the default that attributes had to be manually white-listed via a attr_accessible in order for mass assignment to succeed. Prior to this, the default was for attributes to be assignable unless they were explicitly black-listed attr_protected or any other attribute was white-listed with attr_acessible.

Related

Editing a has_one association in ActiveAdmin - avoid saving when nothing is entered

I've got a model in which a very small percentage of the objects will have a rather large descriptive text. Trying to keep my database somewhat normalized, I wanted to extract this descriptive text to a separate model, but I'm having trouble creating a sensible workflow in ActiveAdmin.
My models look like this:
class Person < ActiveRecord::Base
has_one :long_description
end
class LongDescription < ActiveRecord::Base
attr_accessible :text, :person_id
belongs_to :person
validates :text, presence: true
end
Currently I've created a form for editing the Person model, looking somewhat like this:
form do |f|
...
f.inputs :for => [
:long_description,
f.object.long_description || LongDescription.new
] do |ld_f|
ld_f.input :text
end
f.actions
end
This works for adding/editing the LongDescription object, but I still have an issue: I'd like to avoid validating/creating the LongDescription object if no text is entered.
Anyone with better ActiveAdmin skills than me know how to achieve this?
Are you using accepts_nested_attributes_for :long_description? If so, you can add a :reject_if option:
class Person < ActiveRecord::Base
has_one :long_description
accepts_nested_attributes_for :long_description, reject_if: proc { |attrs| attrs['text'].blank? }
end
Note that this is a Rails thing, not an ActiveAdmin thing, and so it will simply skip assignment and update/create of the nested object if that attribute is missing.
More here: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

Multiple parameters for form_for()

I'm reading Beginning Rails 3. It creates a blog with Users who can post Articles and also post Comments to these Articles. They look like this:
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation
attr_accessor :password
has_many :articles, :order => 'published_at DESC, title ASC',
:dependent => :nullify
has_many :replies, :through => :articles, :source => :comments
class Article < ActiveRecord::Base
attr_accessible :body, :excerpt, :location, :published_at, :title, :category_ids
belongs_to :user
has_many :comments
class Comment < ActiveRecord::Base
attr_accessible :article_id, :body, :email, :name
belongs_to :article
in app/views/comments/new.html.erb there's a form which begins like this:
<%= form_for([#article, #article.comments.new]) do |f| %>
My confusion lies in why form_for() has two parameters. What do they resolve to and why are they necessary?
thanks,
mike
Actually, in your example, you are calling form_forwith one parameter (which is Array). If you check the documentation you will see parameters it expects: form_for(record, options = {}, &proc).
In this case a record can be ActiveRecord object, or an Array (it can be also String, Symbol, or object that quacks like ActiveRecord). And when do you need to pass it an Array?
The simplest answer is, when you have a nested resource. Like in your example, you have defined Article has many Comments association. When you call rake routes, and have correctly defined routes, you will see that Rails has defined for you different routes for your nested resource, like: article_comments POST /article/:id/comments.
This is important, because you have to create valid URI for your form tag (well not you, Rails does it for you). For example:
form_for([#article, #comments])
What you are saying to Rails is: "Hey Rails, I am giving you Array of objects as a first parameter, because you need to know the URI for this nested resource. I want to create new comment in this form, so I will give you just initial instance of #comment = Comment.new. And please create this comment for this very article: #article = Article.find(:id)."
This is roughly similar to writing:
form_for(#comments, {:url => article_comments_path(#aticle.id)})
Of course, there is more to the story, but it should be enough, to grasp the idea.
This is a form for commenting on an article. So you, you need the Article you're commenting on (#article) and a new Comment instance (#article.comments.new). The form action for this form will be something like:
/articles/1/comments
It contains the id of the article you're commenting on, which you can use in your controller.
If you omit the #article like this: form_for #article.comments.new, the form action will looks like this:
/comments
In the controller you would have no way of knowing to which article the comment belongs.
Note that for this to work, you need to define a nested resource in your routes file.

Rails setting OR conditions in validate_presence_of in a model?

In a rails model, is it possible to do something like
class Example < ActiveRecord::Base
#associations
validates_presence_of :item_id, (:user_id OR :user_email)
#functions
end
Where the model has 3 columns of :item_id, :user_id, and :user_email?
I want the model to be valid as long as I have a :user_id or a :user_email.
Idea being that if the item is recommended to a person who isn't currently signed up, it can be associated via email address for when the recommended person signs up.
Or is there a different method that I can use instead?
One approach is to wrap those fields as a virtual attribute, say:
class Example < ActiveRecord::Base
validates_presence_of :referral
def referral
user_id || user_email
end
end
or you can just throw a custom validate validation method. See custom validations on the Rails API
If both user_id and user_email come from another model, perhaps it's better to add the association instead
class Example
belongs_to :user
validates_associated :user
before_validate :build_user_from_id_or_email
def build_user_from_id_or_email
# ... Find something with the parameters
end
end
validates_presence_of :item_id
validates_presence_of :user_id, :if => Proc.new{ |x| x.user_email.blank? }
validates_presence_of :user_email, :if => Proc.new{ |x| x.user_id.blank? }

Rails AR validates_uniqueness_of against polymorphic relationship

Is it posible to validate the uniqueness of a child model's attribute scoped against a polymorphic relationship?
For example I have a model called field that belongs to fieldable:
class Field < ActiveRecord::Base
belongs_to :fieldable, :polymorphic => :true
validates_uniqueness_of :name, :scope => :fieldable_id
end
I have several other models (Pages, Items) which have many Fields. So what I want is to validate the uniqueness of the field name against the parent model, but the problem is that occasionally a Page and an Item share the same ID number, causing the validations to fail when they shouldn't.
Am I just doing this wrong or is there a better way to do this?
Just widen the scope to include the fieldable type:
class Field < ActiveRecord::Base
belongs_to :fieldable, :polymorphic => :true
validates_uniqueness_of :name, :scope => [:fieldable_id, :fieldable_type]
end
You can also add a message to override the default message, or use scope to add the validation:
class Field < ActiveRecord::Base
belongs_to :fieldable, :polymorphic => :true
validates_uniqueness_of :fieldable_id, :scope => [:fieldable_id, :fieldable_type], :message => 'cannot be duplicated'
end
As a bonus if you go to your en.yml, and enter:
activerecord:
attributes:
field:
fieldable_id: 'Field'
You are going to replace the default 'subject' that rails add to the errors with the one you specify here. So instead of saying: Fieldable Id has been already taken or so, it would say:
Field cannot be duplicated

How to create nested objects using accepts_nested_attributes_for

I've upgraded to Rails 2.3.3 (from 2.1.x) and I'm trying to figure out the accepts_nested_attributes_for method. I can use the method to update existing nested objects, but I can't use it to create new nested objects. Given the contrived example:
class Product < ActiveRecord::Base
has_many :notes
accepts_nested_attributes_for :notes
end
class Note < ActiveRecord::Base
belongs_to :product
validates_presence_of :product_id, :body
end
If I try to create a new Product, with a nested Note, as follows:
params = {:name => 'Test', :notes_attributes => {'0' => {'body' => 'Body'}}}
p = Product.new(params)
p.save!
It fails validations with the message:
ActiveRecord::RecordInvalid: Validation failed: Notes product can't be blank
I understand why this is happening -- it's because of the validates_presence_of :product_id on the Note class, and because at the time of saving the new record, the Product object doesn't have an id. However, I don't want to remove this validation; I think it would be incorrect to remove it.
I could also solve the problem by manually creating the Product first, and then adding the Note, but that defeats the simplicity of accepts_nested_attributes_for.
Is there a standard Rails way of creating nested objects on new records?
This is a common, circular dependency issue. There is an existing LightHouse ticket which is worth checking out.
I expect this to be much improved in Rails 3, but in the meantime you'll have to do a workaround. One solution is to set up a virtual attribute which you set when nesting to make the validation conditional.
class Note < ActiveRecord::Base
belongs_to :product
validates_presence_of :product_id, :unless => :nested
attr_accessor :nested
end
And then you would set this attribute as a hidden field in your form.
<%= note_form.hidden_field :nested %>
That should be enough to have the nested attribute set when creating a note through the nested form. Untested.
check this document if you use Rails3.
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#label-Validating+the+presence+of+a+parent+model
Ryan's solution is actually really cool.
I went and made my controller fatter so that this nesting wouldn't have to appear in the view. Mostly because my view is sometimes json, so I want to be able to get away with as little as possible in there.
class Product < ActiveRecord::Base
has_many :notes
accepts_nested_attributes_for :note
end
class Note < ActiveRecord::Base
belongs_to :product
validates_presence_of :product_id unless :nested
attr_accessor :nested
end
class ProductController < ApplicationController
def create
if params[:product][:note_attributes]
params[:product][:note_attributes].each { |attribute|
attribute.merge!({:nested => true})
}
end
# all the regular create stuff here
end
end
Best solution yet is to use parental_control plugin: http://github.com/h-lame/parental_control

Resources