So I have a model that uses an integer to track it’s state which can be one of several predetermined states – proposed, accepted, rejected – which I’ve mapped to an integer. I’ve used ruby magic to make lots of syntactically pretty methods so that I can do things like @proposal.accepted? and can transition it like @proposal.rejected!. It’s all data driven so that I can add new states easily. Off the top of my head:
@@states = {1 => 'Proposed', 2 => 'Accepted', 3 => 'Rejected'} @@states.each_pair do |k,v| define_method "#{v.downcase}?" { self.state_id == k } define_method "#{v.downcase}!" { self.state_id = k } end
That’s all great but in a related table I find the need to select only related objects of a certain state. What I initially got is something like this:
has_many accepted_proposals ... conditions => 'proposals.state_id = 2'
Now if there’s one thing I’ve learned to hate it’s magic numbers sprinkled around the code. What I want is to write something like:
has_many accepted_proposals ... conditions => ['proposals.state_id = ?', Proposal.accepted_state_id]
And so I tried to define it like my other useful utility methods except … you can’t use define_method to make a class method, only an instance method. I didn’t like this but then while discussing it with a colleague I had the thought – method_missing! I’ve never had cause to use to use it before but the idea is pretty simple. Insert your own code that can pattern match against a method name that isn’t previously defined and do something useful. And what you end up with is something like this:
@@state_ids = Hash.new @@states.each_pair do |k,v| @@state_id[v.downcase] = k end def self.method_missing(method_sym, *arguments, &block) if method_sym.to_s =~ /^(.*)_state_id$/ @@state_ids[$1] else super end end
So adding a new state to the class variable effectively gives me 3 automagically generated methods, @proposal.state?, @proposal.state! and Proposal.state_state_id. Very sweet.
Now of course I can do this in a more traditional way with a class method that would look something like Proposal.state_id(‘accepted) but that seems vaguely unsatisfying while the method_missing approach just reeks of awesome.
Follow me on Twitter
Sorry, the comment form is closed at this time.