Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using method missing in Rails

I have a model with several date attributes. I'd like to be able to set and get the values as strings. I over-rode one of the methods (bill_date) like so:

  def bill_date_human
    date = self.bill_date || Date.today
    date.strftime('%b %d, %Y')
  end
  def bill_date_human=(date_string)
    self.bill_date = Date.strptime(date_string, '%b %d, %Y')
  end

This performs great for my needs, but I want to do the same thing for several other date attributes... how would I take advantage of method missing so that any date attribute can be set/get like so?

like image 411
tybro0103 Avatar asked Jan 16 '12 20:01

tybro0103


People also ask

What is method missing in ruby?

method_missing is a method that ruby gives you access inside of your objects a way to handle situations when you call a method that doesn't exist. It's sort of like a Begin/Rescue, but for method calls. It gives you one last chance to deal with that method call before an exception is raised.

What is method_ missing?

method_missing(symbol [, *args] ) → result. Invoked by Ruby when obj is sent a message it cannot handle. symbol is the symbol for the method called, and args are any arguments that were passed to it. By default, the interpreter raises an error when this method is called.

What is Define_method in ruby?

define_method is a method defined in Module class which you can use to create methods dynamically. To use define_method , you call it with the name of the new method and a block where the parameters of the block become the parameters of the new method.

What is MetaProgramming in ruby?

Metaprogramming is a technique in which code operates on code rather than on data. It can be used to write programs that write code dynamically at run time. MetaProgramming gives Ruby the ability to open and modify classes, create methods on the fly and much more.


1 Answers

As you already know signature of desired methods it might be better to define them instead of using method_missing. You can do it like that (inside you class definition):

[:bill_date, :registration_date, :some_other_date].each do |attr|
  define_method("#{attr}_human") do
    (send(attr) || Date.today).strftime('%b %d, %Y')
  end   

  define_method("#{attr}_human=") do |date_string|
    self.send "#{attr}=", Date.strptime(date_string, '%b %d, %Y')
  end
end

If listing all date attributes is not a problem this approach is better as you are dealing with regular methods instead of some magic inside method_missing.

If you want to apply that to all attributes that have names ending with _date you can retrieve them like that (inside your class definition):

column_names.grep(/_date$/)

And here's method_missing solution (not tested, though the previous one is not tested either):

def method_missing(method_name, *args, &block)
  # delegate to superclass if you're not handling that method_name
  return super unless /^(.*)_date(=?)/ =~ method_name

  # after match we have attribute name in $1 captured group and '' or '=' in $2
  if $2.blank?
    (send($1) || Date.today).strftime('%b %d, %Y')
  else
    self.send "#{$1}=", Date.strptime(args[0], '%b %d, %Y')
  end
end

In addition it's nice to override respond_to? method and return true for method names, that you handle inside method_missing (in 1.9 you should override respond_to_missing? instead).

like image 125
KL-7 Avatar answered Sep 27 '22 22:09

KL-7