Attr_Accessor Strongly Typed Ruby on Rails

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 by Proc.new
  • lambda, which is created by the keyword lambda

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



Leave a reply



Submit