Overriding attribute getters in ActiveRecord models

I’m trying to do subj, but getting wrong results. Let’s consider a
model Person with one attrubute “full_name”. I wrote the following
code to compose full name of first name and last name:

class Person < ActiveRecord::Base
attr_accessor :first_name
attr_accessor :last_name

def full_name
unless first_name.nil? || last_name.nil?
“#{first_name} #{last_name}”
else
super
end
end
end

Such “overriding” does not work while persisting. Tests are better
than any words:

def test_full_name_through_first_and_last_names [SUCCESS]
person = Person.new(:first_name => “John”, :last_name => “Smith”)
assert_equal “John S.”, person.full_name
end

def test_full_name [SUCCESS]
person = Person.new(:full_name => “John S.”)
assert_equal “John S.”, person.full_name
end

def test_full_name_persisting [! FAILED !]
person = Person.create!(:first_name => “John”, :last_name =>
“Smith”)
assert_equal “John S.”, Person.find(person.id).full_name
end

Can somebody please explain mechanism of attributes getters and
setters? How and when they are injected into the model class? Why
don’t Rails use them when getting data to persist? And how to override
them?

Sorry, I couldn’t find correspondent code in Rails sources.

I revealed another interesting fact: database row contains just "
" (space) as the “full_name” attribute. It means that somehow
last_name and first_name are EMPTY when ActiveRecord calling full_name
method. WTF?!

On Aug 13, 8:47 am, Daniel V. [email protected] wrote:

I revealed another interesting fact: database row contains just "
" (space) as the “full_name” attribute. It means that somehow
last_name and first_name are EMPTY when ActiveRecord calling full_name
method.

Well the key thing is that when you set first_name or last_name all
that is happening is an instance variable is being set. Nowhere are
you propagating that to fullname.
You might also run into problems because attribute accessor methods
are only generated on the fly.

Fred

Thanks, Fred, you are right, I found place in the Rails code where
attributes are reading and they are using instance_variable, not a
“getter” method:

def read_attribute(attr_name)
attr_name = attr_name.to_s
if !(value = @attributes[attr_name]).nil? <— that’s it

as you see, applying here to instance variable avoids any attempts of
overriding attribute reading procedure.

On 13 Aug 2008, at 10:13, Daniel V. wrote:

Thanks, Fred, you are right, I found place in the Rails code where
attributes are reading and they are using instance_variable, not a
“getter” method:

def read_attribute(attr_name)
attr_name = attr_name.to_s
if !(value = @attributes[attr_name]).nil? <— that’s it

Yup that’s it, attributes are stored in @attributes

as you see, applying here to instance variable avoids any attempts of
overriding attribute reading procedure.

Not sure what you mean there. It’s still perfectly possible to
overwrite accessors.

Fred

I solved problem by setting instance variable in before_save:

class Person < ActiveRecord::Base
attr_accessor :first_name
attr_accessor :last_name

def full_name
unless first_name.nil? || last_name.nil?
“#{first_name} #{last_name}”
else
super
end
end

before_save :set_full_name

def set_full_name
self.full_name = full_name
end
end