Dynamic scope for accessing Model Attributes - ruby-on-rails

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)

Related

Advanced ruby on rails models and validation

In general, I have a website which needs to have complex registration process. And in that registration process I need to include 4 tables from database.
Now... I cannot validate one by one model and to enter 4 of them in database. Is there a way to make common points of all those tables in one model?
Let's say:
User model has columns: username, name, etc.
Package model has: type, account_number
etc
And in registration process I need to include username, name, account_number and to validate them. How to do that?
Without seeing your model structure, this is just speculation, but here goes:
--
Virtual Attributes
In your User model, you can use attr_accessor to create a set of virtual attributes - which basically mean you can create a series of setter / getter methods in your User model.
Although I don't think this will help you directly, it should give you an idea as to how you can create single-model validation:
#app/models/user.rb
Class User < ActiveRecord::Base
attr_accessor :new, :values, :to, :validate
validates, :new, :values, :to, :validate, presence: true
end
This will allow you to create the attributes in the User model - and although they won't be saved, you can then use them to validate against
attr_accessor
To give you a quick overview of this method, you first need to remember that Rails is just a collection of modules and classes.
This means that every model you load is just a class which has been populated with a series of getter / setter methods. These methods are defined by ActiveRecord from your data table columns, and are why you can call #user.attribute
The magic is that if you use attr_accessor, you'll basically create your own attributes in your User model - which won't be saved in the database, but will be treated like the other attributes your objects have, allowing you to validate them
Because your registration process seems to be complex, I would go even futher as virtual attributes and use Form Objects
7 Patterns to Refactor Fat ActiveRecord Models
LA Ruby Conference 2013 Refactoring Fat Models
ActiveModel Form Objects
I understand that you multistep registration. You shouldn't create 4 models only because your view pages needs it. You should:
remove validation from User model and add validation on each form
create 4 different forms (for example extends by ActiveModel or user gem reform)
add validation to each form
after form.valid? save part of user info to #user object
Thats all.

Any difference between :model and :model_id in attr_accessible for Rails/ActiveRecord?

When I create a scaffold and I need to have a belongs_to relation to another model, I add a field called model_id (replacing model with that model's name):
rails generate scaffold Grade user_id:integer subject_id:integer letter:string
Then in the above Grade model, I might add:
belongs_to :user
belongs_to :subject
Rails automatically adds user_id and subject_id to the list of attr_accessible fields. Do I do any harm by also adding :user and :subject to the list of attr_accessible fields so that I can mass assign using those as well?
attr_accessible is intended to protect against mass-assignment attacks that come from data that is externally sent to your application. In most cases you're probably doing things like this in your create & update actions:
#model = Model.new(params[:model])
or
#model.update_attributes(params[:model])
You should ask yourself why you'd have one form that uses subject_id and another that uses subject. The only real harm here is inconsistency, which can actually be pretty detrimental to large projects. If you follow the convention that all forms will use the actual database column (subject_id), then you'll save yourself some headache in the future when you can't remember out why you did it two different ways.
If you're updating attributes through the console, you can either use update_attributes(params[:model], without_protection: true) or a gem I wrote called sudo_attributes which lets you do sudo_update_attributes(params[:model]).
I think it doesn't hurt you, but will bring a kind of mess in your code

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"

Mass assign for attr_accessible :association_attributes in special cases

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.

Resources