a brief overview on using the read_attribute and write_attribute methods, and their shorthands, for overriding default setter/getter behaviour in Railsno comments no links
Even in quite deliberate past attempts to whittle these articles down to something much more bloggy and svelt, I've failed sensationally, and almost without exception ended up spilling two thousand plus words onto a page that begs for few more than half that. I'll try to remedy this although, as must be painfully evident, I'm already failing miserably.
THAT SAID -- this is going to be the first stab at a series of reasonably quick "bet you didn't know but now you do"-style how-to articles, which should deliver the kind of wham-bam-thank-you-ma'am attitude that modern web audiences crave, and should surely leave you feeling violated and devoid of self-worth.
Sound good? Onto it then --
modifying getters, or "how to keep the barking of vicious poodles to a minimum"
Have you ever gotten stuck in an infinite loop in one of your models, by trying to alter the behavior of a model attribute inside the method that defines or is named identically as the attribute itself? Now that you think about it, are you surprised? I hope not, and I hope that the invertiable Picadilly Circus into which you mercilessly jammed your Rails app was lesson enough for both you and it.
No, there's nothing inherently wrong with altering or appending to the default methods that Rails provides you with, you just have to do it right.
This isn't right:
#!/app/models/poodle.rb class Poodle < Dog #...some other code here def bark if angry? bark + "woof woof woof!" end end end
Did you guess why? Yes, invariably our poor Poodle's bark will continue to add to itself until either your server has crashed or the poodle (Craig, we'll call him) has died. Either way it's a messy scenario, and poor Craig (and/or your server administrator) will be deceased from exhaustion.
If you're really feeling like a hero today, here's how to save Craig's life:
#!/app/models/poodle.rb class Poodle < Dog #...some other code here def name "Craig" end def bark if angry? read_attribute(:bark) + "woof woof woof!" end end end
Now when Craig's REALLY angry he'll bark only 3 extra times rather than 3300 extra times, which should certainly leave you some extra time and energy to stroll ol' Craig down to the park for some lady-hunting (I'm presuming here, of course, that you'd be using Craig to try to ATTRACT women, rather than kill them, as the latter would be both sinister and counter-productive).
You might have noticed that there's a flaw in the above code, though, as it assumes that all poodles are named "Craig". Well -- in my world they are, and as confounding and terrible as that might seem, this is my hell, and I have no business imposing it upon you.
Of course there's an alternative syntax for read_attribute(:bark) that's considered a bit of a shorthand:
def bark if angry? self[:bark] + "woof woof woof!" end end
self, in this instance, referring to the instantiation of this particular Craig/Poodle, and [:bark] referring to the bark attribute thereof. This may be a better way to go, too, as I've heard some rumblings that the read_attribute method will soon be depreciated, and that the self[:attribute] syntax will become the Rails default, but as read_attribute is still defined in the Rails API under ActiveRecord::Base, I've no reason to believe that this is true.
So that takes care of our getters for accessing and reassigning values to attributes within a model, but what about the setters?
modifying setters, or "how to adjust your code and lifestyle to better accommodate your pets' mental disorders"
Okay, so suppose that you've got two dogs. The other one, fittingly enough, is a rather vain Irish Setter who compulsively wears coloured bandanas, and prefers that you switch up the color every day. So as you're going to be doing a lot of bandana-changing, you'll likely want to write a setter attribute that ensures that you're not giving the dog the same color on two consecutive days (as this infuriates and enrages him).
Luckily enough for you, you've got the write_attribute method to help you with the job:
#!/app/models/irish_setter.rb class IrishSetter < Dog def name "Plumbob Wilson" end def bandana=(color) unless bandana == color.to_s write_attribute(:bandana, color) else raise BandanaError, "PLUMBOB WILSON WORE THAT COLOR YESTERDAY!!!" end end end
Granted, this might more elegantly have been executed using a custom validation of some sort, but for the sake of argument, and ensuring beyond a shadow of a doubt that we appease the vicious Plumbob Wilson, our purpose is served.
As you might imagine, write_attribute has a shorthand method just as read_attribute does. Voila:
def bandana=(color) self[:bandana] = color end
No, this particular implmentation of the method doesn't change the default behaviour at all, but for our purposes it does the job.
Yes, in the phantasmagoric psuedo-reality that is my life, Poodles are named Craig, and Irish Setters have OCD and are named Plumbob Wilson. Hopefully you're beginning to understand the origins of my psychotic ramblings.
the tutorial's over, leave now
As at the end of Ferris Bueller's Day Off, you can consider this the part of the article where I come out of the shower in a towel, ask you what you're still doing here, and tell you to go home. If you're already at home, then you're fine, but there's really nothing more to read here.
Can you believe that I got through this article covering only one topic, and somewhat concisely no less? Neither can I. Originally this article was going to cover three separate topics, thus likely making it at least thrice as long, but I thought the better of it and managed to land somewhere within the vicinity of mankind.
Granted, this rambling is really only bloating and distending the article at this point, but you've learned (hopefully) how to modify setters and getters inside of Rails models, and are, by default, a greater humon than you were even two minutes ago. Kudos, Ralph Machio, you're a friggin' genius.