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.

Sorry, the comment form is closed at this time.

© 2011 nestoriak.com Suffusion theme by Sayontan Sinha