What happens in the background with the following code?
class User < ActiveRecord::Base
attr_accessor :name
attr_accessible :name
end
Hint: When instantiating the class, will it be persisted to the database? Why or why not?
attr_accessor is ruby code and is used when you do not have a column in your database, but still want to show a field in your forms. The only way to allow this is to attr_accessor :fieldname and you can use this field in your View, or model, if you wanted, but mostly in your View.
attr_accessible allows you to list all the columns you want to allow Mass Assignment, as andy eluded to above. The opposite of this is attr_protected which means this field i do NOT want anyone to be allowed to Mass Assign to. More then likely it is going to be a field in your database that you don't want anyone monkeying around with. Like a status field, or the like.
In most cases, you don't need to use attr_accessor if the field is a column in the users table in your database. ActiveRecord will figure it out for you.
attr_accessible simply allows to field to be assigned via mass assignment (e.g., with update_attributes). This is good for security purposes. More information from the MassAssignmentSecurity API docs.
Thanks everyone for quick answers!
Your answers combined gave me the pieces I needed to understand this puzzle, I think.
(In a related problem, I was getting a lot of nil errors like "Object doesn’t support #inspect", and "undefined method ‘keys’ for nil:NilClass". I managed to solve it now, by removing the att_accessor field altogether.)
By experimenting with this particular case, this is what I've found out:
Actually, the :name field won't be persisted to the database.
user = User.new(:name=>"somename")
Will only set the attribute on the object, but not persist the :name column to the database. Like the following 'rails console' output shows:
> user
=> <User id: nil, created_at: nil, updated_at: nil>
> user.save
=> true
> user
=> <User id:1, created_at: 2011-01-19 12:37:21, updated_at: 2011-01-19 12:37:21>
I assume this is because *the setter made by attr_accessor will override ActiveRecord's setter* (which takes care of the database persistence). You can still retrieve the value from the :name field from the object though, like this:
> user.name
=> "somename"
So, in conclusion, I've learnt that using attr_accessor on fields might lead to them not being persisted to the database. And while I thought attr_accessible describes fields in the database that should be accessible from the outside, it doesn't seem to make a difference in this case.
Since it inherits ActiveRecord, it will be persisted when you call the save method (but not when it is instantiated).
If you don't have any attributes for that model, I assume ActiveRecord will simply save a new row in the database (i.e. your object will only have a persisted id). This makes sense, as you might later add attributes to your User model, and the persisted instances should still be retrievable.
Related
Just trying out Mongoid at the moment, I've run into an issue that's probably pretty simple but has me at a loss:
I have a really simple Article model:
class Article
include Mongoid::Document
field :title, :type => String
field :content, :type => String
key :title
referenced_in :subject
validates_presence_of :title
end
I added key :title after I had already created one test record. Newly created records work as expected, but the first Article (which originally had the normal mongoid object id) behaves strangely:
In rails views this first article still returns its object id instead of the new key. ie using link_to article.name, article returns:
Show
... when all the rest return the parameterized keys, like:
Show
If I click that link I get "Record not found". I tried loading and resaving this record in the console, and after that calling article.id on that record did return the parameterized key, but it still shows up the old way in the view and doesn't work.
So, a couple questions:
What's going on here?
How do you fix it?
This situation indicates to me that if you set a field on a mongoid model to be the key, you need to be really sure that it will never change. How do you handle something like using the title of an article as a slug, then, when those may occasionally need change?
Thanks!
Well, since _id is immutable, your only option is to reinsert this document with your new 'sluggish' id and delete the old one.
And yes, _id format and shard key (if you use sharding) are the two things you better have right from the beginning :-)
Everything else can be fixed relatively easily.
Here's the code:
class M
include Mongoid::Document
field :name
end
params = { name: "foo", age: 20 }
M.create(params)
#=> #<M name: "My Name", age: 20>
Notice that age wasn't defined, yet it was saved.
This is problematic (potentially a source of DoS) because a malicious user can add any parameters in POST and unknown fields with a large string can sneak in. (e.g. name=foo&bogus=#{'x'*1000000})
So far, I couldn't find anything but attr_accessible, but it's not really great for Mongoid as you have to maintain the same field names in both field and attr_accessible all the time, in all models. Not DRY.
I think the attr_accessible API is great for ActiveRecord, because there a. you don't explicitly define fields in the models (DRY) and b. it's guaranteed there's no chance that a nonexistent field gets saved to RDB. But for Mongoid, I think there should be a better solution than attr_accessible.
Note that there's a global config setting allow_dynamic_fields but it's not about mass assignment so it's out of the scope in this discussion, however I think it should actually be a per-model macro and should also take care of mass-assignment.
How are you dealing with this problem?
I'm always using attr_accessible in models. I rarely found myself including all fields as accessible. Usually there are always a few fields that shouldn't be accessible for mass assignment. If you often need to include every attribute and you're concerned about duplication:
attr_accessible *fields.keys
What I have done to solve this issue, is use a before save callback in my model:
set_callback(:save, :before) do |doc|
(doc.attributes.keys - fields.keys).each { |f| doc.unset(f) }
end
This way, even if there are extra attributes they get removed before being saved.
I'm wondering, if i have a model where all values can be set through a form, do i still need to use attr_accessible ?
The important thing that comes to my mind is the id field (or maybe even the created_at, updated_at) fields.
Should i always whitelist the accessible fields if there is a form around ?
You should think if in the future you will add some importaint data and you will forget to protect it. Using attr_accessible it will be protected by default.
If there are any associations, such as user has_many :roles, :through => :authorization in User model there aren't any data about this association, but hacker can mass_assign roles_attributes. So he will change data in authorizations table through the hole in User model.
Also he will availible to manupalate with simple has_many :dollars associations passing dollar_ids=[1,2,3,4,5,6,7...] via form.
You can checkout also Ryan's screencast about mass assignment
http://railscasts.com/episodes/26-hackers-love-mass-assignment
no, id is explicitly excluded. in fact, including it in attr_accessible still won't let people overwrite it.
but it's still best practice to use attr_accessible, even if you intend to allow people to update everything
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'm trying to figure out how to make this hack for attr_accessible to support a really common use case in my code, but really after looking at attr_acessible and ActiveRecord::Base source code for a while, still don't know where to start. I can probably figure it out after digging deeper, but first I'd like to ask if anyone else would find this hack useful, if there's some other way to do this right now,
Here's the use case:
For many models, the attributes that should be accessible through mass assignment are different when creating the object and when updating the object. One simple example is a User model that has two attributes: username, and password. When the object is just being created, I want both username and password to be accessible through mass assignment. After the object is created, only password should be accessible through mass assignment, because it shouldn't be possible to change the username.
Of course I could just set the username for the object manually in my controller's create method, but I find that some version of this case happens with every model. If it was possible to specify a different set of attr_accessible attributes for creates and updates, I could continue to use my standard restful new and create methods (for example as provided by inherited_resources). Besides I think this info belongs in the model.
So here's how I think it could work:
class User < ActiveRecord::Base
attr_accessible :password
attr_accessible_create :email
attr_accessible_update :bio
...
What would happen is that when the object is created, password and email would be accessible through mass assignment. When the object is updated, email and bio would be accessible through mass assignment.
This could also work through black listing via attr_protected. Same example with black listing:
class User < ActiveRecord::Base
attr_accessible :email, :password, :bio
attr_protected_create :bio
attr_protected_update :email
...
Or alternatively the syntax could be more like this:
attr_accessible :password, :create => [:email], :update => [:bio]
With these hacks, you can continue to use User.update_attributes(params[:user]) and User.new(params[:user]) in your controllers and know that the mass assignment stuff is taken care of. If your controllers are created by something like inherited_resources (which seriously rocks and keeps getting better) or resource_controller, you don't have to worry about creating custom controller actions for this simple, common use case.
The questions to you, my expert Rails users:
Is there a way to do this now that I'm overlooking, as a Ruby/Rails newbie?
Would you find this functionality useful, if it were supported?
Which syntax do you like better:
attr_accessible_update and attr_accessible_create
attr_accessible :update => [], :create => []
support both, dude!
Should I make this into a gem?
If you know where the parameters are sanitized during new/create/update/build with attr_accessible/attr_protected values, it would be great if you could point it out.
Thanks!
ActiveRecord has an attr_readonly method that should do what you need.
I'm pretty sure this kind of functionality doesn't currently exist.
If you go ahead and try to implement this as a gem, I'd probably be a bigger fan of the #2 syntax you proposed. As a general rule now, you can only use either attr_protected or attr_accessible in a model once, and you can't mix them. Adding more attr_* statements might get confusing.
Should you implement this as a gem? Why not? No harm in putting something else out there that at least you find helpful.
Sorry I can't help you on your last point. :)