views:

2548

answers:

6

An example of what I'm talking about:

class Person < ActiveRecord::Base
  def name=(name)
    super(name.capitalize)
  end
  def name
    super().downcase  # not sure why you'd do this; this is just an example
  end
end

This seems to work, but I was just read the section on overriding attribute methods in the ActiveRecord::Base docs (http://rails.rubyonrails.com/classes/ActiveRecord/Base.html), and it suggests using the read_attribute and write_attribute methods. I thought there must be something wrong with what I'm doing in the example above; otherwise, why would they bless these methods as the "right way" to override attribute methods? They're also forcing a much uglier idiom, so there must be a good reason...

My real question: Is there something wrong with this example?

+5  A: 

My real question: Is there something wrong with this example?

Yes

Your model's attribute methods are created dynamically on the Person class, they aren't defined on ActiveRecord::Base. For that reason, your super call won't work and will fail.

Gareth
Actually, that's not true. According to this, http://errtheblog.com/posts/18-accessor-missing, "All told, attribute accessor methods are indeed method_missing magic. Get pumped: super will work when overriding them".
jcnnghm
+1  A: 

Echoing Gareth's comments... your code will not work as written. It should be rewritten this way:

def name=(name)
  write_attribute(:name, name.capitalize)
end

def name
  read_attribute(:name).downcase  # No test for nil?
end
Aaron Longwell
+2  A: 

As an extension to Aaron Longwell's answer, you can also use a "hash notation" to access attributes that have overridden accessors and mutators:

def name=(name)
  self[:name] = name.capitalize
end

def name
  self[:name].downcase
end
mipadi
A: 

I have a rails plugin that makes attribute overriding work with super as you would expect. You can find it on github.

To install:

./script/plugin install git://github.com/chriseppstein/has_overrides.git

To use:

class Post < ActiveRecord::Base

  has_overrides

  module Overrides
    # put your getter and setter overrides in this module.
    def title=(t)
      super(t.titleize)
    end
  end
end

Once you've done that things just work:

$ ./script/console 
Loading development environment (Rails 2.3.2)
>> post = Post.new(:title => "a simple title")
=> #<Post id: nil, title: "A Simple Title", body: nil, created_at: nil, updated_at: nil>
>> post.title = "another simple title"
=> "another simple title"
>> post.title
=> "Another Simple Title"
>> post.update_attributes(:title => "updated title")
=> true
>> post.title
=> "Updated Title"
>> post.update_attribute(:title, "singly updated title")
=> true
>> post.title
=> "Singly Updated Title"
chriseppstein
+2  A: 

There is some great information available on this topic at http://errtheblog.com/posts/18-accessor-missing.

The long and short of it is that ActiveRecord does correctly handle super calls for ActiveRecord attribute accessors.

jcnnghm
A: 

According to the docs the correct way to do this is with read_attribute/write_attribute. The problem with those is that they don't handle things like serialize columns correctly.

class Post < ActiveRecord::Base

  serialize :foo

  def foo
    read_attribute(:foo)
  end
end

Won't work (foo will return the serialized string rather then the deserialized object).

Tim