Overriding attributes in ActiveRecord
I love that super
calls method_missing
if the method is not defined on the superclass.
Consider this case. You have some ActiveRecord named Account
, which has an associated email_address
. However, an account owner may optionally give a special “notification” email address, which will be used for things like newsletter emails and security issues and such. If no notification address has been explicitly given, it should fall back to the account’s primary email address. It’s as simple as this:
1 2 3 4 5 |
class Account < ActiveRecord::Base def notification_address super || email_address end end |
Calling super
forces the superclass, ActiveRecord::Base, to be sent the notification_address
message, which it won’t understand. This causes method_missing
to be called on AR::Base, which looks for the notification_address
attribute in the record’s attribute set. If that has not been set, it will be nil, in which case we then default to the email_address value.
Just as you’d expect.
Reader Comments
It is a neat trick but I can’t help but wonder if this is one of those tricks that will have a obscured meaning 6 months down the road or for another developer that has not thought of the trick. Wouldn’t it be much clearer to say:
class Account < ActiveRecord::Base def notification_address self[:notification_address] || email_address end end
6 Feb 2007
Now if I can just figure out how to make my code example have that nifty look yours does. :)
6 Feb 2007
Eric, I don’t really think “super” is obscure. It means to call the method of the superclass, and that’s really what you want, right? You’re overriding the default definition of “notification_address”, and you want to get at the prior definition (which just happens to be implemented in the superclass via method_missing).
However, your solution is fine, too, though less DRY. What happens if you decide to rename the attribute to
notification_email
?6 Feb 2007
Jamis,
To me the problem is that from a purely logical standpoint “notification_address” isn’t defined in the superclass. “notification_address” is really a property on “Account” and it is only through an artifact of ActiveRecord::Base that it is able to do the right thing.
When you say “super” you are saying “carry out this same method as my parent defines it”. But ActiveRecord::Base doesn’t really define that attribute. The base class does (automatically of course by the parent class but that is an implementation detail).
Perhaps a hypothetical would better explain what I mean. Currently database attributes are implemented by method_missing. What if ActiveRecord version 20 (i.e. sometime down the road) decided that it is to annoying that a ActiveRecord object don’t answer respond_to? correctly. To fix the problem they change the implementation to actually define the method on the base class instead of relying on method_missing. When they do this your code starts failing. Why? Because your code is dependent on an implementation detail of ActiveRecord. Logically the attribute belongs to “Account” and therefore it is illogical to ask the parent class to carry out the implementation.
Of course my solution is a bit less DRY but that seems like a minor offense compared to assuming implementation details of a 3rd party library.
6 Feb 2007
That’s a compelling argument, Eric. I think I still prefer using
super
, but I can certainly see why you might prefer to grab the attribute directly from the record.6 Feb 2007
Hi!
Sorry if I always need some more time to understand your examples, but how would you specify “the notification_address attribute in the record’s attribute set”?
How, and BTW, how do you get “that nifty look” for code examples, as Eric A. asked?
Have a good day, Eric
6 Feb 2007
Eric Anderson says what I wanted to say, but better. If there was a “previous_definition” keyword, that would be appropriate, but super doesn’t mean what it means in this example.
If we’re talking about dry, some “default_attribute_value” metaprogramming would really be the best way to go. Clarity and DRY, best of both worlds.
6 Feb 2007
Eric D, more or less just as Eric A did: self[:notification_address] will grab the attribute directly from the record, without going through pseudo-accessor that ActiveRecord provides.
Oh, and if you specify some code within <macro:code lang=”ruby”>...<macro:code> tags, it’ll be syntax-highlighted as ruby code.
6 Feb 2007
“Just as you’d expect.”
Well that’s the problem, isn’t it? Why would we expect this to work the way it does unless we were aware of the specific implementation details of AR? This trick is cool, but it looks like it could be almost more magic than it should be.
(Yes, I am more or less just adding my support to Eric A’s argument).
6 Feb 2007
Sigh.
Alright, calm down, people. It seemed obvious to me, even without knowing how activerecord works, that super would do what it does.
Why?
Because, in my mind, I’m overriding the default behavior of an attribute. And where is that default behavior defined? In the superclass. And how do you call the previous implementation of something from a subclass? Via super.
It seemed plain to me. I apologize if it is not so plain to everyone else.
If it isn’t plain to you, don’t use it.
6 Feb 2007
Jamis, please don’t get discouraged that the people in your comments don’t agree with you – most of us that like your tricks are too busy implementing them in our own applications to say anything about them. =)
6 Feb 2007
Neat. Seems obvious, but for some reason, most probably out of habit, I always use
ActiveRecord::Base#read_attribute
.Eric: Way too much discussion for what amounts to “very leet but possibly less obvious. Apply as desired according to your own coding style and guidelines.” I’d also argue using
#read_attribute
is even more clear than square bracket operators, and is also documented in AR::Base under “overriding default accessors”.6 Feb 2007
I think the test for a super method is, whether it’s implemented by the super class.
Would other classes implementing ActiveRecord::Base have this method? If so, it’s a super method. If not, it’s part of the derived class.
It’s a simple test that could keep you out of trouble.
I love method_missing, I use it often, but I also think there’s a huge propensity for overuse that leads to fragile code. This is one place where I would advocate thinking twice and only using it as last resort.
I would always write read_attribute :email_address instead of assuming how Base implements its method_missing.
Just today I had to deal with this sort of method_missing magic when trying to use Net::SFTP inside Rake. Not pleasent: http://blog.labnotes.org/2007/02/06/method_missing-best-saved-for-last/
6 Feb 2007
Joe, thanks for the kind words. I’d like to say here that I certainly don’t mind people disagreeing with me; it’s a great exercise for me to think hard about what I’m saying, and helps to form more concrete boundaries for my opinions.
That said, everything I say on this blog should considered to have an implicit disclaimer before each one: “your mileage may vary.” Much of Rails is about aesthetics, and as such, what looks beautiful to one might look otherwise to someone else. If you feel to disagree with something I say, by all means, do. But at least think hard about what I say, first. I will do likewise upon reading the comments posted here.
Whether we succeed in convincing one another, well…we can’t hope for too much, now, can we? :)
6 Feb 2007
Jamis,
First off, I find your writings invaluable, great stuff.
I think these posts often create very nice discussions. I think it’s important that when you’re working with other people it is sometimes better to be a little more verbose, use a little less magic just to make it easier for someone new to the project (or even rails) to understand what’s going on.
I find this a recurring issue with Ruby code. If the code is well documented, with examples (not just API docs) you can have some beautiful and easy/natural to use code. You can create some really cool DSLs with just Ruby: Rails, Capistrano, Rake, Builder etc. But if you look at the source of those projects, I find them quite hard to understand, since a lot of stuff relies on method_missing and other more obscure techniques. I don’t want to call this “good” or “bad”, it’s just something to think about whenever you do something cool with Ruby/Rails’ more advanced features. I’ve just started some projects with people new to rails, so it’s definitely something I need to think about.
7 Feb 2007
Ok, sorry if I’m really slow, but we don’t play in the same league you and I!
So, if I got it right, there should be a “notification_address” column in accounts table, which can be left nil. I tried without this column, and it obviously didn’t work.
Thanks for the tip anyway, I hope I’ll leave the Nuby world some day thanks to your pieces of advice!
8 Feb 2007
Maybe I am missing something, but this doesn’t seem to work quite right with “int” fields.
For instance, say I have an unset attribute “products_count”
products_count # => 0 read_attribute(“products_count”) # => nil
Is this right, or is there something I’m missing here?
13 Feb 2007