How i can set instance variable using mass assignment in Rails - ruby-on-rails

What I have:
class User < ActiveRecord::Base
attr_accessor :notify
end
User.new(args)
What I'm doing:
User.new(args)
User.notify = true
What I need is something like:
User.new(args.merge(:notify => true))

What you have shown will work. Make sure to include :notify in the list of attributes for attr_accessible if you are using that.

As long as you have the attr_accessor defined, including the variable in any ActiveRecord mass assignment will work. As "Wizard of Ogz" mentioned, limiting access to the attributes affects both database attributes and instance variables alike.

Related

How to understand the colon operator usage in a Ruby class

I am learning both Ruby (2.3.x) & Rails (4.x). I was going through the Ruby On Rails Tutorial and I encountered this syntax and am having trouble reading it:
class User < ApplicationRecord
validates :name, presence: true
validates :email, presence: true
end
Does this class define validates as a method which takes in a :name symbol and a hash presence:true? The same thing applies to line 3.
Or it is something entirely else? All attempts to run it resulted in:
uninitialized constant ApplicationRecord.
I looked at the source(maybe?) but am still not clear.
This is a special DSL introduced by ApplicationRecord. What you are actually doing is calling those methods inside class during declaration. It adds those validations to your class, so whenever you try to save a record it will fail if you don't have email or name
Try this
user = User.new
user.save
user.valid? # false
And try to do the same without validates.
If it will make things more clear for you, you can try write this class like this
class User < ApplicationRecord
validates(:name, presence: true)
validates(:email, presence: true)
end
validates is implemented as a class method in ActiveModel::Validations.
The ActiveModel::Validations module is included in ApplicationRecord, therefore you are able to call that method when your User class is loaded.
validates accepted an array and treats the last element of that array as an options hash (if the last element is an hash).
validates is a pre-defined helper that Active Record offers to use in Rails to make the validation work easier, this way you can with some single lines of code manage several validations of several attributes.
As it's a helper within Rails it's also a method defined in the ActiveModel module, in the core of the framework, see: active_model/validations.rb
The most common is the presence attribute which you're facing the trouble with, that specifies that the attribute used isn't empty, doing it in the Ruby way through the blank? method to check if the value passed isn't blank nor nil.

Set non-database attribute for rails model without `attr_accessor`

In PHP, I can set an attribute (that is not a column in database) to a model. E.g.(PHP code),
$user = new User;
$user->flag = true;
But in rails, when I set any attribute that doesn't exist in database, it will throw an error undefined method flag. There is attr_accessor method, but what will happen if I need about ten temp attributes?
but what will happen if I need about ten temp attributes?
#app/models/user.rb
class User < ActiveRecord::Base
attr_accessor :flag, :other_attribute, :other_attribute2, :etc...
end
attr_accessor creates "virtual" attributes in Rails -- they don't exist in the database, but are present in the model.
As with attributes from the db, attr_accessor just creates a set of setter & getter (instance) methods in your class, which you can call & interact with when the class is initialized:
#app/models/user.rb
class User < ActiveRecord::Base
attr_accessor :flag
# getter
def flag
#flag
end
# setter
def flag=(val)
#flag = val
end
end
This is expected because it's how ActiveRecord works by design. If you need to set arbitrary attributes, then you have to use a different kind of objects.
For example, Ruby provides a library called OpenStruct that allows you to create objects where you can assign arbitrary key/values. You may want to use such library and then convert the object into a corresponding ActiveRecord instance only if/when you need to save to the database.
Don't try to model ActiveRecord to behave as you just described because it was simply not designed to behave in that way. That would be a cargo culting error from your current PHP knowledge.
As the guys explained, attr_accessor is just a quick setter and getter.
We can set our Model attr_accessor on record initializing to be a Ruby#Hash for example using ActiveRecord#After_initilize method so we get more flexibility on temporarily storing values (idea credit to this answer).
Something like:
class User < ActiveRecord::Base
attr_accessor :vars
after_initialize do |user|
self.vars = Hash.new
end
end
Now you could do:
user = User.new
#set
user.vars['flag'] = true
#get
user.vars['flag']
#> true
All that attr_accessor does is add getter and setter methods which use an instance variable, eg this
attr_accessor :flag
will add these methods:
def flag
#flag
end
def flag=(val)
#flag = val
end
You can write these methods yourself if you want, and have them do something more interesting than just storing the value in an instance var, if you want.
If you need temp attributes you can add them to the singleton object.
instance = Model.new
class << instance
attr_accessor :name
end

Rails already has getters and setters without me setting an attr_accessor. How do I stop all setters ?

Here is my class, as you can see, no attr_accessor
class LegacyBlogPost < ActiveRecord::Base
establish_connection Rails.configuration.database_configuration['blogs']
self.table_name = 'blogpost'
self.primary_key = 'post_id'
end
But I can still read and write all variables. Is this default behavior ?
x = LegacyBlogPost.find(172925)
x.title += "."
x.save
=> true
My main question though, is how do I avoid ever writing to this class ?
I understand I could make the mysql user only be allowed to do select statements. Also I belive I could re-write the rails setters to just return as soon as the method is hit. But I had assumed if I just set attr_reader's for everything, the writers just wouldn't be there.
ActiveRecord automatically creates getters\setters to read\write for all columns
If you want to make the model a read only do
class LegacyBlogPost < ActiveRecord::Base
def readonly?
return true
end
end
To prevent it from being destroyed you can use
def before_destroy
raise ActiveRecord::ReadOnlyRecord
end
credit http://blog.zobie.com/2009/01/read-only-models-in-activerecord/
Look into using attr_accessible.

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

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