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?
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.
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.
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.
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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With