How to prevent updating a single attribute in Rails? - ruby-on-rails

When a form is submitted, how to prevent a single attribute from being updated in Rails? All other attributes should be updated.
Is it before_save, attr_reader or some other way?
If using before_save, how to access to the attributes hash?
Rails 3.0.7

Check out attr_protected.
Class YourModel << ActiveRecord::Base
attr_protected :the_one_column, as: :update
# ...
end
Now as part of a call to update_attributes, you'd specify the :update role, for example
klass = YourModel.find(some_id)
klass.update_attributes(params[:your_model], as: :update)
If :the_one_column is set in params passed to update_attributes, it will throw an error.
As #Beerlington mentioned in his comment to your Question, you should also check out attr_accessible. It's generally better to spend the 30 minutes going through all models of your application white-listing attributes using attr_accessible than it is to blacklist specific attributes with attr_protected.

Other option is simply doing this in your controller:
klass.update_attributes( params[:your_model].except(:attributes_to_avoid) )

Related

Best way to ensure a Model attribute is consistently downcased and stripped?

I'd like to know what the best way to ensure a user supplied parameter is downcased and stripped in all situations.
I would like to achieve the following:
Guarantee that the attribute will not be saved to the DB unless stripped/downcased
Queries against the db should always downcase/strip the attribute
Validations are run against a downcased/stripped version of user supplied params
Models return downcase/stripped attribute (which shouldn't be a problem given item #1)
you need to write a before_save callback method, within which you downcase and strip the attributes set by the user.
For eg:
class User < ActiveRecord::Base
before_save :format_values
def format_values
self.name = self.name.downcase
end
end
EDIT
I had missed your 3rd point about validations. So if you need to also run validations on these values. You'd need to use the before_validation callback instead.
class User < ActiveRecord::Base
before_validation :format_values
def format_values
self.name = self.name.strip.downcase if name
end
end
Updated the answer based on the comment.
No need to get fancy with callbacks (don't use callbacks, anyway). Just override the setter for your attribute.
class MyModel
def some_attribute=(value)
value = value.strip.downcase if value
write_attribute(:some_attribute, value)
end
end
You would do that in a before_validation callback:
# in your model
before_validation :normalize_attribute
private
def normalize_attribute
# change `attribute` to your actual attribute's name
self.attribute = attribute.strip.downcase if attribute
end
Or you could do that with a custom setter:
# change `attribute` to your actual attribute's name
def attribute=(value)
write_attribute(:attribute, value.strip.downcase) if value
end
The first option will sanitize the attribute's value every time the object is saved, even if the value has not changed. This might be helpful if you introduce this sanitize method when records in the database already exist, because this allow to sanitize all existing record with just one line of code in the Rails console: Model.find_each(&:save). The second option will only sanitize values when they are set. This is a bit more performant.
I both cases I suggest to check for if attribute otherwise you might call strip.downcase on nil values what would lead to an exception.

Rails 4 upgrading - attr_accessor

I'm upgrading my application from rails 3.2 to rails 4.2.5.
In one of my model i have attr_accessor :user_id and i have defined a getter and setter methods, as i need to value on conditional basic.
class DummyModel < ActiveRecord::Base
attr_accessor :user_id
belongs_to :current_user
before_save :set_user_id
def user_id
self[:user_id] ? self[:user_id] : current_user_id ? current_user.id : nil
end
def user_id=(value)
self[:user_id] = value ? value : current_user_id ? current_user.id : nil
end
private
def set_user_id
self.current_user_id = CurrentUser.first_or_create(:user_id => self[:user_id]).id
end
end
In the above DummyModel, that table doesn't have user_id. But a referance current_user_id which needs to be update by the params user_id which i get from browser.
So when i do DummyModel.new(params[:dummy_model]), it will try to assign the params[:dummy_model][:user_id] to the attr_accessor (With the setter method = user_id=(value)). Once assigned it can be used in the before_save.
This whole thing was working properly in rails 3.2. But when i tried to update to rails 4.2, im getting this error message, as assigning value for attr_accessor with self[:user_id] syntax is removed.
ActiveModel::MissingAttributeError: can't write unknown attribute `user_id`
So i have one solution to rename the user_id to something else, but user_id is used is many places of my application its not possible to change.
Hope this explains my problem. Thanks for the help in advance.
I'm not sure why you're doing this but there's no point using attr_accessor if you're going to replace the methods it is generating. You may as well use the instance variable directly, for example
def user_id
#user_id || current_user_id
end
def user_id=(value)
#user_id = value || current_user_id
end
If you prefer attr_accessible, you could use it in Rails 4 too. You should install it like gem:
gem 'protected_attributes'
What i can understand from the above but in rails 4 now there are strong parameters. attr_accessor still can be initialized in rails 4 and that still gives you virtual attributes, but from above it looks like user_id is a physical attribute on the model level. Let me know if this makes sense.
I think you are confused about rails 3 attr_accessible and attr_accessor.
Link about difference between them: https://stackoverflow.com/a/6958433/1306709
In rails 4 you still can use attr_accessor, but if you meant attr_accessible here is some info: strong parameters

Rails how to set a temporary variable that's not a database field

For my app, I have different signup entry points that validate things differently.
So in the main signup, nothing is required except for the email and password field. In an alternative signup field, many more are required. So in the user model I have
validate_presence_of :blah, :lah, :foo, :bah, :if => :flag_detected
def flag_detected
!self.flag.nil?
end
I want to set that flag through the controller. However that flag isn't a database field. I'm just wondering if this is achievable in Rails or there is something wrong with the way that I am thinking about this? If so, what's the best way to achieve this? Thanks.
What you need is attr_accessor
class User < ActiveRecord::Base
attr_accessor :flag
attr_accessible :flag # if you have used attr_accessible or attr_protected else where and you are going to set this field during mass-assignment. If you are going to do user.flag = true in your controller's action, then no need this line
end
basically attr_accessor :flag create the user.flag and user.flag = ... methods for your model.
and attr_accessible is for mass-assignment protection.
Following up on the best practice debate:
Create a method that does what you want. I.e. save_with_additional_validation. This is much more clear and self-documenting code and works the same way. Just call this method instead of save()
It seems like you need to define setter method
class User < ActiveRecord::Base
attr_accessible :flag
def flag=(boolean)
boolean
end
end

Difference between attr_accessor and attr_accessible

In Rails, what is the difference between attr_accessor and attr_accessible? From my understanding, using attr_accessor is used to create getter and setter methods for that variable, so that we can access the variable like Object.variable or Object.variable = some_value.
I read that attr_accessible makes that specific variable accessible to the outside world.
Can someone please tell me whats the difference
attr_accessor is a Ruby method that makes a getter and a setter. attr_accessible is a Rails method that allows you to pass in values to a mass assignment: new(attrs) or update_attributes(attrs).
Here's a mass assignment:
Order.new({ :type => 'Corn', :quantity => 6 })
You can imagine that the order might also have a discount code, say :price_off. If you don't tag :price_off as attr_accessible you stop malicious code from being able to do like so:
Order.new({ :type => 'Corn', :quantity => 6, :price_off => 30 })
Even if your form doesn't have a field for :price_off, if it's in your model it's available by default. This means a crafted POST could still set it. Using attr_accessible white lists those things that can be mass assigned.
Many people on this thread and on google explain very well that attr_accessible specifies a whitelist of attributes that are allowed to be updated in bulk (all the attributes of an object model together at the same time)
This is mainly (and only) to protect your application from "Mass assignment" pirate exploit.
This is explained here on the official Rails doc : Mass Assignment
attr_accessor is a ruby code to (quickly) create setter and getter methods in a Class. That's all.
Now, what is missing as an explanation is that when you create somehow a link between a (Rails) model with a database table, you NEVER, NEVER, NEVER need attr_accessor in your model to create setters and getters in order to be able to modify your table's records.
This is because your model inherits all methods from the ActiveRecord::Base Class, which already defines basic CRUD accessors (Create, Read, Update, Delete) for you.
This is explained on the offical doc here Rails Model and here Overwriting default accessor (scroll down to the chapter "Overwrite default accessor")
Say for instance that: we have a database table called "users" that contains three columns "firstname", "lastname" and "role" :
SQL instructions :
CREATE TABLE users (
firstname string,
lastname string
role string
);
I assumed that you set the option config.active_record.whitelist_attributes = true in your config/environment/production.rb to protect your application from Mass assignment exploit. This is explained here : Mass Assignment
Your Rails model will perfectly work with the Model here below :
class User < ActiveRecord::Base
end
However you will need to update each attribute of user separately in your controller for your form's View to work :
def update
#user = User.find_by_id(params[:id])
#user.firstname = params[:user][:firstname]
#user.lastname = params[:user][:lastname]
if #user.save
# Use of I18 internationalization t method for the flash message
flash[:success] = t('activerecord.successful.messages.updated', :model => User.model_name.human)
end
respond_with(#user)
end
Now to ease your life, you don't want to make a complicated controller for your User model.
So you will use the attr_accessible special method in your Class model :
class User < ActiveRecord::Base
attr_accessible :firstname, :lastname
end
So you can use the "highway" (mass assignment) to update :
def update
#user = User.find_by_id(params[:id])
if #user.update_attributes(params[:user])
# Use of I18 internationlization t method for the flash message
flash[:success] = t('activerecord.successful.messages.updated', :model => User.model_name.human)
end
respond_with(#user)
end
You didn't add the "role" attributes to the attr_accessible list because you don't let your users set their role by themselves (like admin). You do this yourself on another special admin View.
Though your user view doesn't show a "role" field, a pirate could easily send a HTTP POST request that include "role" in the params hash. The missing "role" attribute on the attr_accessible is to protect your application from that.
You can still modify your user.role attribute on its own like below, but not with all attributes together.
#user.role = DEFAULT_ROLE
Why the hell would you use the attr_accessor?
Well, this would be in the case that your user-form shows a field that doesn't exist in your users table as a column.
For instance, say your user view shows a "please-tell-the-admin-that-I'm-in-here" field.
You don't want to store this info in your table. You just want that Rails send you an e-mail warning you that one "crazy" ;-) user has subscribed.
To be able to make use of this info you need to store it temporarily somewhere.
What more easy than recover it in a user.peekaboo attribute ?
So you add this field to your model :
class User < ActiveRecord::Base
attr_accessible :firstname, :lastname
attr_accessor :peekaboo
end
So you will be able to make an educated use of the user.peekaboo attribute somewhere in your controller to send an e-mail or do whatever you want.
ActiveRecord will not save the "peekaboo" attribute in your table when you do a user.save because she don't see any column matching this name in her model.
attr_accessor is a Ruby method that gives you setter and getter methods to an instance variable of the same name. So it is equivalent to
class MyModel
def my_variable
#my_variable
end
def my_variable=(value)
#my_variable = value
end
end
attr_accessible is a Rails method that determines what variables can be set in a mass assignment.
When you submit a form, and you have something like MyModel.new params[:my_model] then you want to have a little bit more control, so that people can't submit things that you don't want them to.
You might do attr_accessible :email so that when someone updates their account, they can change their email address. But you wouldn't do attr_accessible :email, :salary because then a person could set their salary through a form submission. In other words, they could hack their way to a raise.
That kind of information needs to be explicitly handled. Just removing it from the form isn't enough. Someone could go in with firebug and add the element into the form to submit a salary field. They could use the built in curl to submit a new salary to the controller update method, they could create a script that submits a post with that information.
So attr_accessor is about creating methods to store variables, and attr_accessible is about the security of mass assignments.
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.
Let's consider the following example
class Address
attr_reader :street
attr_writer :street
def initialize
#street = ""
end
end
Here we have used attr_reader (readable attribute) and attr_writer (writable attribute) for accessing purpose. But we can achieve the same functionality using attr_accessor. In short, attr_accessor provides access to both getter and setter methods.
So modified code is as below
class Address
attr_accessor :street
def initialize
#street = ""
end
end
attr_accessible allows you to list all the columns you want to allow Mass Assignment. The opposite of this is attr_protected which means this field I do NOT want anyone to be allowed to Mass Assign to. More than 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 two words:
attr_accessor is getter, setter method.
whereas attr_accessible is to say that particular attribute is accessible or not. that's it.
I wish to add we should use Strong parameter instead of attr_accessible to protect from mass asignment.
Cheers!
A quick & concise difference overview :
attr_accessor is an easy way to create read and write accessors in
your class. It is used when you do not have a column in your database,
but still want to show a field in your forms. This field is a
“virtual attribute” in a Rails model.
virtual attribute – an attribute not corresponding to a column in the database.
attr_accessible is used to identify attributes that are accessible
by your controller methods makes a property available for
mass-assignment.. It will only allow access to the attributes that you
specify, denying the rest.

attr_accessible in rails Active Record

When I use the attr_accessible to specify which fields from my Model I will expose, is it true for script/console as well? I mean something that I didn't specify as attr_accessible won't be accessible as well through console ?
This is only true for mass assignment. For instance, if you were to set attr_protected :protected in your model:
>> Person.new(:protected => "test")
=> #<Person protected: nil>
Conversely, you could set all attributes you want as accessible using attr_accessible.
However, the following will still work:
>> person = Person.new
=> #<Person protected: nil>
>> person.protected = "test"
=> #<Person protected: "test">
This is the same behaviour as in controllers, views, etc. attr_protected only protects against mass assignment of variables, primarily from forms, etc.
The console behaves exactly as your Rails application. If you protected some attributes for a specific model, you won't be able to mass assign these attributes either from console or from the Rails app itself.
I found why:
Specifies a white list of model attributes that can be set via mass-assignment, such as new(attributes), update_attributes(attributes), or attributes=(attributes).
This is the opposite of the attr_protected macro:
Mass-assignment will only set attributes in this list, to assign to the rest of
attributes you can use direct writer methods. This is meant to protect sensitive
attributes from being overwritten by malicious users tampering with URLs or forms.
If you‘d rather start from an all-open default and restrict attributes as needed,
have a look at `attr_protected`.
So it means that it just avoid mass-assignment but i can still set a value.
When you specify somethings to be attr_accessible only those things can be accessed in console or by website Interface.
eg: Suppose you made name and email to be attr_accessible:
attr_accessible :name, :email
and left out created_at and updated_at (which you are supposed to).
Then you can only edit/update those fields in console.
If you want to expose a field form your model, you can use
attr_accessor :meth # for getter and setters
attr_writer :meth # for setters
attr_reader :meth # for getters
or if you want add some behaviour to your attribute, you ll have to use virtual attributes
def meth=(args)
...
end
def meth
...
end
cheers.

Resources