Mass assign for attr_accessible :association_attributes in special cases - ruby-on-rails

In the User model I have two accepts_nested_attributes_for: :details (which is has_one association) and :membership_orders (has_many).
For :details I have:
attr_accessible :details_attributes
But for the membership_orders I can't have so simple accessor, because I want to protect it from the normal user, but make it accessible for the admin.
It's possible to do with attribute-permissions plugin (github.com/Fingertips/attribute-permissions/tree/master), but I think it's not the finest solution.
Can you tell me how I can add special expression for the attr_accessible, or maybe filter out those attributes using before_validation (or what-else).
You can read about this problem by this link: blog.smartlogicsolutions.com/2009/02/24/rails-23-nested-object-forms-im-not-crazy-about-them/
Thanks.

You could certainly filter them out before validation. Another option is to switch to attr_protected and specify the fields you want protected instead of the ones you want open. That could be a simple switch or a more involved one, depending on the size of your models.

Related

How to extend model class?

I have two types of users (regular user, super user). What is the proper way to extend one base user class with additional tables?
I was thinking something like this but I am not sure am I going to right direction:
class User < ActiveRecord::Base
end
class SuperUser < User
end
class RegularUser < User
end
Is this the proper way to do it in Rails? Thanks :)
It is 100% correct approach, however you need to remember, that all your models will be stored in one table in database. This approach is called STI (Single table inheritance) and requires only one additional field type in you model.
If you want to have different types of users I would go with user roles versus different user tables etc.
A very good gem for that is CanCan and the documentation is excellent:
https://github.com/ryanb/cancan/wiki/Role-Based-Authorization
You will also have nice helpers as .can? or .cannot? and more.
Yes, and you should also use single table inheritance. What this means is you should add a column called 'type' to your user model. Rails recognizes the column 'type' and treats it special. Essentially, all entries in your type model will reference another model. In that model, you can define rules for each type. It would also be a good idea to validate your user model so that only the two types you want can be entered. This should work:
class User < ActiveRecord::Base
validates :type, :inclusion => {:in => ['SuperUser', 'RegularUser']}
end

Dynamic scope for accessing Model Attributes

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)

If attr_accesible is not set, can somebody change the id?

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

Prevent certain properties from being updated?

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"

Hacking attr_accessible to support a different set of accessible attributes for create and update

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. :)

Resources