I'm pretty new to rails, building my first 'real' app and struggling to understand how to make relationships work properly.
I have a Quiz model and and Icon model. Each Quiz belongs_to an Icon, an Icon has_many Quizzes. (think of an icon as a category).
On my "new/edit" Quiz forms I want a select box to choose the correct Icon. At the moment I have...
<%= collection_select(:quiz, :icon_id, Icon.all, :id, :title, :prompt => true) %>
And in my Quiz controller create action I have...
def create
#icon = Icon.find(params[:quiz][:icon_id])
#quiz = #icon.quizzes.build(params[:quiz])
if #quiz.save
flash[:success] = "New quiz created successfully!"
redirect_to #quiz
else
render 'new'
end
end
When I submit the form I get a
Can't mass-assign protected attributes: icon_id
error which I understand as icon_id isn't assigned as being attr_accessible in the model.
I could either make it accessible as there is no real security risk around this or I could remove the icon_id from the quiz hash before passing to the build method but both these options don't seem like the right way to do things.
What is the right way to do this?
Thanks!
Just put
attr_accessible :icon_id
in your Quiz model.
From the Ruby on rails api: attr_accessible: Specifies a white list of model attributes that can be set via mass-assignment.
TL;DR: Rails has a feature called mass assignment, which is what you are doing when you pass in that params[:quiz] hash. You need to specify attr_accessible for any attributes you wish to update using mass assignment.
A quick history lesson:
It used to be that all attributes were mass assignable by default, so your code would have worked just fine.
A number of months ago, there was a highly publicized episode at github where somebody was able to exploit this feature by constructing a post body with something to the effect of user[:admin] = true. This effectively gave the user admin access, because the application didn't prevent just anybody from setting admin = true. There was a way to prevent this, but the developers missed it.
The response by Rails to this was to make all attributes protected by default, forcing the developer to explicitly specify any fields available to be updated via mass assignment. I believe this was in the 3.2.3 release.
Related
I'm currently using the mass assignment security baked into rails 3 to scope what level of users can update about their model. For example this code allows me to protect attributes based on the user level.
class Customer
attr_accessor :name, :credit_rating
attr_accessible :name
attr_accessible :name, :credit_rating, :as => :admin
end
I would like to be able to use this same idea for which attributes appear when I do a find. For example I would like to be able to say
Customer.all.as(:admin)
and get back the credit rating. Compare this to doing
Customer.all
and getting back all the attributes except the credit_rating
Is this something rails supports and I've missed?
attr_accessible is used to filter incoming attributes on mass assignment. This is a convenience method created so that a developer does not need to manually clean the incoming hash of params, something he does not control.
When displaying information a developer is in full control of what he/she desires to show, so there seems to be no reason to limit the read functionality.
However, rails allows you to "select" the attributes you desire in a query: see http://guides.rubyonrails.org/active_record_querying.html#selecting-specific-fields
You could easily create a scope with the name admin that would limit the selected values.
If you do not desire to have the full models, but only the values, you could use the generated sql. e:g.
ActiveRecord::Base.connection.select_values(Customer.select('name').to_sql)
Hey everybody I have a scenario here I am really trying to figure out. I am trying to build a custom blog in Rails which requires users to be signed in to leave a comment. Basically I have 3 models - Post, User, and Comment. My problem is mainly with the comment model.
I am trying to have it so that a Comment belongs_to a User and also belongs_to Post, which have many comments. I also have made the post_id and the user_id inaccessible as I do not want them to be tampered with (I want the comment to be automatically associated with the id of the post on which it was left, and I want the user to be automatically determined via session). My problem is that I am not really sure how I can create a valid comment. If I try to add a comment doing #blog.comments.build(#attr) I am ignoring the inacessible User, and of course if I try to build it through the User I am ignoring the blog.
My guess is there is a much better way to do this and I might just be approaching it wrong. Any ideas?
I assume you have this in the Comment model:
attr_accessible :content
and when you try to build a comment this happens:
#post = Post.first
#post.comments.build(:user=>current_user)
# => WARNING: Can't mass-assign protected attributes: user
So that won't work.
If you want to protect the user_id and post_id from being overwritten on an update, you could do this:
attr_accessible :content, :user_id, :post_id
attr_readonly :user_id, :post_id
#post = Post.first
#post.comments.build(:user=>current_user)
#post.save!
# sets user_id when creating
#post.user_id = 37
#post.save!
# saves, but user_id is not changed. No warning is logged.
#post.update_attributes(:user_id=>37)
# same as above
#post.update_attribute(:user_id,37)
# raises ActiveRecord::ActiveRecordError: user_id is marked as readonly
But this seems like overkill, since presumably your application would not submit a form with a user_id for an existing comment, and someone would have to code up their own form and post it to change the ID.
You could always just set the relation manually:
comment = #post.build(#attr)
comment.user = #user
comment.save
At least, I'm pretty sure that would work.
In rails, when updating a model, how do you prevent certain properties of the model from being updated when using a call like:
#user.update_profile params[:user]
Since anyone can just create a form input with a name like 'password', how can you filter the set of properties that you are allowing to be updatable?
Is this what attr_XXX is for?
You're looking for attr_accessible. It lets you specify which attributes can be set through mass-updating (like update_attributes), but you'll still be able to set the attributes "manually" (ie #user.attribute = ...).
For more information, see The importance of attr_accessible in Ruby on Rails.
You're looking for attr_protected to black list any attributes you don't want altered in a bulk update.
Throw it in your model and give it a list of attribute symbols to blacklist.
class User < ActiveRecord::Base
attr_protected :password
end
Alternatively you can use attr_accessible to take the white list approach and only the attributes given can be updated when updating the entire record at once. Every other attribute will be protected.
N.B Protected attributes can still be overwritten if it's directly assigned to as in
#user.password = "not secure"
I've been reading up on rails security concerns and the one that makes me the most concerned is mass assignment. My application is making use of attr_accessible, however I'm not sure if I quite know what the best way to handle the exposed relationships is. Let's assume that we have a basic content creation/ownership website. A user can have create blog posts, and have one category associated with that blog post.
So I have three models:
user
post: belongs to a user and a category
category: belongs to user
I allow mass-assignment on the category_id, so the user could nil it out, change it to one of their categories, or through mass-assignment, I suppose they could change it to someone else's category. That is where I'm kind of unsure about what the best way to proceed would be.
The resources I have investigated (particularly railscast #178 and a resource that was provided from that railscast), both mention that the association should not be mass-assignable, which makes sense. I'm just not sure how else to allow the user to change what the category of the post would be in a railsy way.
Any ideas on how best to solve this? Am I looking at it the wrong way?
UPDATE: Hopefully clarifying my concern a bit more.
Let's say I'm in Post, do I need something like the following:
def create
#post = Post.new(params[:category])
#post.user_id = current_user.id
# CHECK HERE IF REQUESTED CATEGORY_ID IS OWNED BY USER
# continue on as normal here
end
That seems like a lot of work? I would need to check that on every controller in both the update and create action. Keep in mind that there is more than just one belongs_to relationship.
Your user can change it through an edit form of some kind, i presume.
Based on that, Mass Assignment is really for nefarious types who seek to mess with your app through things like curl. I call them curl kiddies.
All that to say, if you use attr_protected - (here you put the fields you Do Not want them to change) or the kid's favourite attr_accessible(the fields that are OK to change).
You'll hear arguments for both, but if you use attr_protected :user_id in your model, and then in your CategoryController#create action you can do something like
def create
#category = Category.new(params[:category])
#category.user_id = current_user.id
respond_to do |format|
....#continue on as normal here
end
OK, so searched around a bit, and finally came up with something workable for me. I like keeping logic out of the controllers where possible, so this solution is a model-based solution:
# Post.rb
validates_each :asset_category_id do |record, attr, value|
self.validates_associated_permission(record, attr, value)
end
# This can obviously be put in a base class/utility class of some sort.
def self.validates_associated_permission(record, attr, value)
return if value.blank?
class_string = attr.to_s.gsub(/_id$/, '')
klass = class_string.camelize.constantize
# Check here that the associated record is the users
# I'm leaving this part as pseudo code as everyone's auth code is
# unique.
if klass.find_by_id(value).can_write(current_user)
record.errors.add attr, 'cannot be found.'
end
end
I also found that rails 3.0 will have a better way to specify this instead of the 3 lines required for the ultra generic validates_each.
http://ryandaigle.com/articles/2009/8/11/what-s-new-in-edge-rails-independent-model-validators
I'd like to create a user registration form where the user ticks some boxes that do not connect to the model.
For example, there might be a 'terms & conditions' box that I don't want to have a boolean field in the User model saying 'ticked Terms & Conditions'. Instead I want to create a record somewhere else (like a transaction) that recorded the date/time they accepted the T&Cs.
Another example might be some preference they indicated that I'll use later and hold in the session for now, like 'remember me'.
I can mix these types of fields with the regular form helper. How could I do either one of the examples above when using formtastic? It kind of sticks to have to mix traditional rails tags with lovely clean formtastic code.
You can create any number of virtual attributes in your model that do not necessarily need to be tied to a database column. Adding attr_accessor :terms_and_conditions to your user model will make this 'field' available to formtastic -- even though it's not a database field. You can validate it like any other field or create your own setter method to create a record elsewhere if that's what you need.
I'm inclined to disagree with the approach to use attr_accessors for action-specific entry elements. If Ts&Cs need to be recorded then that makes sense, but sometimes you need data that really is unrelated to the model and is only related to the specific action at hand, such as 'perform some heavyweight operation when executing the action'.
Lets say you have a sign-up form, and you're not using OAuth, and you have an option to specify twitter username and password on sign up. This is fine:
<%= form.input :twitter_username %>
<%= form.input :twitter_password, :as => :password %>
But this bit below confuses me -- its like formtastic in this case is actually taking away what is already there. Is there a way of adding params[:your-object] while still getting formastic to do all its lovely layout stuff?
How about:
class User < ActiveRecord::Base
...
#I don't want this here. Its only for UserController#create.
#attr_accessor :tweet_when_signed_up
...
end
and:
<%= form.input :tweet_when_signed_up, :as => :checkbox, :param_only => true %>
param_only is my made-up suggestion. It says 'this isn't even a transient property. Its just for this action.
class UserController < ActionController::Base
...
def create
if params[:tweet_when_signed_up] # haven't done this yet -- == 1 or !.nil?
Tweeter.tweet( ... )
end
#user = User.create( params[:user] )
end
The ability to do this is probably there -- does anyone know how to do effectively what I think is a good idea above?
thanks!
Instead I want to create a record
somewhere else (like a transaction)
that recorded the date/time they
accepted the T&Cs.
Use the attr_accessor that bensie describes to integrate the field with formtastic.
Formtastic is about view logic, while the relationship are more model logic. Don't worry about creating the related record in the form. Instead, use callbacks like before_update and after_save in the model to ensure the related record has been created.