attr_accessor strongly typed Ruby on Rails
TL;DR: No it's not possible ... and long answer, yes it is possible, read the metaprogramming section :)
Ruby is a dynamic language, that's why you won't get compile time type warnings/errors as you get in languages like C#.
Same as you can't specify a type for a variable, you can't specify a type for attr_accessor
.
This might sound stupid to you coming from .NET, but in the Ruby community, people kind of expect you to write tests. If you do so, these types of problems will basically vanish. In Ruby on Rails, you should test your models. If you do so, you won't really have any trouble with accidentaly assigning something somewhere wrong.
If you're talking about ActiveRecord in Ruby on Rails specifically, assigning a String into an attribute which is defined as an Integer in the database will result in exception being thrown.
By the way, according to convention, you shouldn't use CamelCase
for attributes, so the correct class definition should be
class Person
attr_accessor :first_name
attr_accessor :last_name
attr_accessor :home_address
end
class Address
attr_accessor :address_line1
attr_accessor :city
attr_accessor :country
end
One reason for this is that if you Capitalize the first letter, Ruby will define a constant instead of a variable.
number = 1 # regular variable
Pi = 3.14159 # constant ... changing will result in a warning, not an error
Metaprogramming hacks
By the way, Ruby also has insanely huge metaprogramming capabilities. You could write your own attr_accessor
with a type check, that could be used something like
typesafe_accessor :price, Integer
with definition something like
class Foo
# 'static', or better said 'class' method ...
def self.typesafe_accessor(name, type)
# here we dynamically define accessor methods
define_method(name) do
# unfortunately you have to add the @ here, so string interpolation comes to help
instance_variable_get("@#{name}")
end
define_method("#{name}=") do |value|
# simply check a type and raise an exception if it's not what we want
# since this type of Ruby block is a closure, we don't have to store the
# 'type' variable, it will 'remember' it's value
if value.is_a? type
instance_variable_set("@#{name}", value)
else
raise ArgumentError.new("Invalid Type")
end
end
end
# Yes we're actually calling a method here, because class definitions
# aren't different from a 'running' code. The only difference is that
# the code inside a class definition is executed in the context of the class object,
# which means if we were to call 'self' here, it would return Foo
typesafe_accessor :foo, Integer
end
f = Foo.new
f.foo = 1
f.foo = "bar" # KaboOoOoOoM an exception thrown here!
or at least something along these lines :) This code works! Ruby allows you to define methods on the fly, which is how attr_accessor
works.
Also blocks are almost always closures, which means I can do the if value.is_a? type
without passing it as a parameter.
It's too complicated to explain here when this is true and when it's not. In short, there are different types of blocks
Proc
, which is created byProc.new
lambda
, which is created by the keywordlambda
one of the differences is that calling return
in a lambda
will only return from the lambda itself, but when you do the same thing from a Proc
, the whole method around the block will return, which is used when iterating, e.g.
def find(array, something)
array.each do |item|
# return will return from the whole 'find()' function
# we're also comparing 'item' to 'something', because the block passed
# to the each method is also a closure
return item if item == something
end
return nil # not necessary, but makes it more readable for explanation purposes
end
If you're into this kind of stuff, I recommend you check out PragProg Ruby Metaprogramming screencast.
Attribute accessor with a datatype
You can make a custom setter like
def field_name=(value)
# check for type and dont assign it if its the wrong typ
# or raise an error
end
just off of the top of my head
Also, duplicate question: attr_accessor strongly typed Ruby on Rails
Difference between attr_accessor and attr_accessible
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.
Why does attr_accessor clobber the existing variables in this model in Ruby on Rails?
When you add an attr_accessor to a class, it defines two methods on it, e.g. User#postcode and User#postcode=.
If the name of the accessor is equal to a name of a model attribute, things break (if you're not careful). When you assign attributes to the model, User#postcode= is called and in your case it does nothing except
@postcode = value
So the value just gets stored in an instance variable and doesn't appear in the attributes hash.
Whereas in a normal scenario (without an accessor) this would go to method_missing and eventually trigger something like
write_attribute(:postcode, value)
And then it would appear in your model's attributes.
Hope that makes sense.
What is attr_accessor in Ruby?
Let's say you have a class Person
.
class Person
end
person = Person.new
person.name # => no method error
Obviously we never defined method name
. Let's do that.
class Person
def name
@name # simply returning an instance variable @name
end
end
person = Person.new
person.name # => nil
person.name = "Dennis" # => no method error
Aha, we can read the name, but that doesn't mean we can assign the name. Those are two different methods. The former is called reader and latter is called writer. We didn't create the writer yet so let's do that.
class Person
def name
@name
end
def name=(str)
@name = str
end
end
person = Person.new
person.name = 'Dennis'
person.name # => "Dennis"
Awesome. Now we can write and read instance variable @name
using reader and writer methods. Except, this is done so frequently, why waste time writing these methods every time? We can do it easier.
class Person
attr_reader :name
attr_writer :name
end
Even this can get repetitive. When you want both reader and writer just use accessor!
class Person
attr_accessor :name
end
person = Person.new
person.name = "Dennis"
person.name # => "Dennis"
Works the same way! And guess what: the instance variable @name
in our person object will be set just like when we did it manually, so you can use it in other methods.
class Person
attr_accessor :name
def greeting
"Hello #{@name}"
end
end
person = Person.new
person.name = "Dennis"
person.greeting # => "Hello Dennis"
That's it. In order to understand how attr_reader
, attr_writer
, and attr_accessor
methods actually generate methods for you, read other answers, books, ruby docs.
When to use attr:accessors in place of a permanent column in rails?
You can use attr_accessor
if you need virtual attributes in model.
For eg: In contact us form you need not to see form data, but you need to send that data using email. So you can create attr_accessor
for adding virtual attributes and can also apply validations on that.
class Contact
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :email, :content
validates_presence_of :name
validates_format_of :email, :with => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
validates_length_of :content, :maximum => 500
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
ref
attr_accessible
is to white list of attributes that can be mass assigned in model.
class Comment < ActiveRecord::Base
attr_accessible :user_id, :content
end
def create
#so here params[:comment], have all parameters. But if those params are not in attr_accessible list it will not save it.
# you can try that by remove attr_accessible from model
@comment = Comment.new(params[:comment])
if @comment.save
flash[:notice] = "Successfully created comment."
redirect_to @comment
else
render :action => 'new'
end
end
Comment Form:
<%= form_for @comment do |f| %>
<%= f.error_messages %>
<%= f.hidden_field :user_id, value: current_user.id %>
<p>
<%= f.label :content %><br />
<%= f.text_area :content %>
</p>
<p><%= f.submit %></p>
<% end %>
Happy Coding...
Using attr_accessor and attr_accessible on the same field
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.
rails 4 mass assignment issue with strong params and attr_accessor
Your device model already has an implicit department_id
by virtue of this line:
belongs_to :department
By additionally declaring attr_accessor :department_id
you are overwriting this implicit attribute (and its ActiveRecord persistence magic) with a concrete getter and setter based on a @department_id
instance variable (which has no persistence magic at all). That's probably not what you had in mind.
So when you perform the mass assignment, the @department_id
value will get changed, but the underlying belongs_to
association will not. Hence your observation that the department association is not updated in the database.
To summarize: you don't need attr_accessor :department_id
because ActiveRecord generates something similar to it automatically when you declare belongs_to :department
.
Rails strong parameters - accepting the virtual attribute
According to these params
{"name"=>"New", "virtual_attr"=>{"enable"=>"false", "start"=>"false"}, "controller"=>"my_model", "action"=>"create"}
You need to change strong params to:
def my_model_params
params.permit(:name, virtual_attr: [:enable, :start])
end
Related Topics
Class Method VS Constant in Ruby/Rails
How to Spawn an Eventmachine "Inside" a Rails App
Making Multiple Http Requests Asynchronously
How to Use Watir::Waiter::Wait_Until to Force Chrome to Wait
Param Is Missing or the Value Is Empty: User Rails 4
Ruby - Keyword Arguments - Can You Treat All of the Keyword Arguments as a Hash? How
Ruby: Compare 2 Arrays for Matches, and Count the Number of Match Instances
Getting Webpage Content with Ruby -- I'm Having Troubles
Rails: Plus Sign in Get-Request Replaced by Space
Full Url for an Image-Path in Rails 3
Search Multiple Models at Once with Ransack
Logging All Method Calls in a Rails App
Ruby Gem Not Found Although It Is Installed