Sunday, April 13, 2008

Rails attr_accessor is an instance variable

It took me some time to truly understand rails attr_accessor macro method though I've already used it a lot before.

Attr_accessor, along with attr_reader and attr_writer, they are in fact ruby things not rails. Attr_accessor defines instance variables and builds the get/set methods for each instance variable.

To know the difference between ActiveRecord's attribute (for table field) and the attr_accessor, the best way is to give an example, let's see:

class User > ActiveRecord::Base
attr_accessor :password #password is not a user's table field
validates_presence_of :login
end

##########################

[/home/jack/test_app]$ script/console
Loading development environment (Rails 2.0.2)
>> user = User.new(:login => "jimmy", :password => "railsrocks")
=> #
### password is not a field

>> user.instance_variables
=> ["@attributes_cache", "@attributes", "@password", "@new_record"]
>> user.instance_variable_get :@login
=> nil
>> user.instance_variable_get :@password
=> "railsrocks"
>> user.has_attribute? "login"
=> true
>> user.has_attribute? "password"
=> false
### login is an attribute, while password is a instance variable

>> user.login
=> "jimmy"
>> user.password
=> "railsrocks"
>> user.send 'login'
=> "jimmy"
>> user.send 'password'
=> "railsrocks"
### both getter/setter are working
### in some situation, for example, the field name itself is a variable
### you can use: user.send method_name

>> user[:login]
=> "jimmy"
>> user.attributes
=> {"login"=>"jimmy"}
>> user[:password]
=> nil
### oops, I can not get password by this way

>> user.attributes={:login => "grissom", :password => "rubyrocks"}
=> {:password=>"rubyrocks", :login=>"grissom"}
>> user.password
=> "rubyrocks"
### well, to assign value is working

3 comments:

Anonymous said...

What is a little nasty about this situation, is if you now say:

user[:password] = 'foo'

what I believe happens is that the :password attribute is created, but since you already have getter/setter called user.password for the instance variable, those getter/setter are not set up for the attribute. This leads to a situation where you can have:

user[:password]
### => 'foo'

user.password
### => 'bar'

Not very nice.

NICOTINA NET said...

Very useful!

Cheers

NICOTINA NET said...
This comment has been removed by the author.