How To Modify Default Setters and Getters in Rails Models
a brief overview on using the read_attribute and write_attribute methods, and their shorthands, for overriding default setter/getter behaviour in Rails
no comments no links ["Even in quite deliberate past attempts to whittle these articles down to\nsomething much more bloggy and svelt, I've failed sensationally, and almost\nwithout exception ended up spilling two thousand plus words onto a page that\nbegs for few more than half that. I'll try to remedy this although, as must be\npainfully evident, I'm already failing miserably.
\n\nTHAT SAID -- this is going to be the first stab at a series of reasonably quick\n\"bet you didn't know but now you do\"-style how-to articles, which should deliver\nthe kind of wham-bam-thank-you-ma'am attitude that modern web audiences crave,\nand should surely leave you feeling violated and devoid of self-worth.
\n\nSound good? Onto it then --
\n\nmodifying getters, or \"how to keep the barking of vicious poodles to a minimum\"
\n\nHave you ever gotten stuck in an infinite loop in one of your models, by trying\nto alter the behavior of a model attribute inside the method that defines or is\nnamed identically as the attribute itself? Now that you think about it, are you\nsurprised? I hope not, and I hope that the invertiable Picadilly Circus into\nwhich you mercilessly jammed your Rails app was lesson enough for both you and\nit.
\n\nNo, there's nothing inherently wrong with altering or appending to the default\nmethods that Rails provides you with, you just have to do it right.
\n\nThis isn't right:
\n", nil, "#!/app/models/poodle.rb\n\nclass Poodle < Dog\n #...some other code here\n\n def bark\n if angry?\n bark + "woof woof woof!"\n end\n end\n\nend
Did you guess why? Yes, invariably our poor Poodle's bark will continue to add to\nitself until either your server has crashed or the poodle (Craig, we'll call him)\nhas died. Either way it's a messy scenario, and poor Craig (and/or your server\nadministrator) will be deceased from exhaustion.
\n\nIf you're really feeling like a hero today, here's how to save Craig's life:
\n", nil, "#!/app/models/poodle.rb\n\nclass Poodle < Dog\n #...some other code here\n\n def name\n "Craig"\n end\n\n def bark\n if angry?\n read_attribute(:bark) + "woof woof woof!"\n end\n end\n\nend
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\nwould be both sinister and counter-productive).
\n\nYou 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.
\n\nOf course there's an alternative syntax for read_attribute(:bark) that's considered a bit of a shorthand:
\n", nil, "def bark\n if angry?\n self[:bark] + "woof woof woof!"\n end\nend
self, in this instance, referring to the instantiation of this particular\nCraig/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\ndefault, but as read_attribute is still defined in the Rails\nAPI under\nActiveRecord::Base, I've no reason to believe that this is true.
\n\nSo that takes care of our getters for accessing and reassigning values to attributes within a model, but what about the setters?
\n\nmodifying setters, or \"how to adjust your code and lifestyle to better accommodate your pets' mental disorders\"
\n\nOkay, 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).
\n\nLuckily enough for you, you've got the write_attribute method to help you with the job:
\n", nil, "#!/app/models/irish_setter.rb\n\nclass IrishSetter < Dog\n\n def name\n "Plumbob Wilson"\n end\n\n def bandana=(color)\n unless bandana == color.to_s\n write_attribute(:bandana, color)\n else\n raise BandanaError, "PLUMBOB WILSON WORE THAT COLOR YESTERDAY!!!"\n end\n end\nend
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.
\n\nAs you might imagine, write_attribute has a shorthand method just as read_attribute does. Voila:
\n", nil, "def bandana=(color)\n self[:bandana] = color\nend
No, this particular implmentation of the method doesn't change the default behaviour at all, but for our purposes it does the job.
\n\nYes, 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.
\n\nthe tutorial's over, leave now
\n\nAs 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.
\n\nCan 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.
\n\nGranted, 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.
\n"]