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 Smith", person.full_name   end

  def test_full_name [SUCCESS]     person = Person.new(:full_name => "John Smith")     assert_equal "John Smith", person.full_name   end

  def test_full_name_persisting [! FAILED !]     person = Person.create!(:first_name => "John", :last_name => "Smith")     assert_equal "John Smith", 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?!

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.

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