Let’s assume you have a class that has some sort of hash where it stores it’s data. We want to calculate amount that we need to pay, but only if number of items that we want to sell is entered. We’ll also assume that price is already present so we don’t need to check for that. It’s easy:
A bit more readable would be:@store[:total] = @store[:price] * @store[:amount] if @store.has_key?(:amount)
This is possible by using method_missing that is cleverly embedded in Ruby method invocation chain. Ruby first tries to find method in calling class, then in it’s super classes and mixins. If all of that fails then it looks into method_missing. Basic usage is covered here. To make upper code possible, original example must be upgraded a little:set_total(price * amount) if amount_exists
The code is ugly and un-refactored, but it’ll work and it will make my class more user friendly. Hopefully I’ll never look again in method_missing method,
def method_missing(method, *params)
method = method.to_s
type = :get
if (method.index('store_').nil? == false) #if you have name conflict use store_ prefix,
#or rename hash key or class method
method.gsub!('store_','')
elsif (method.index('_exists').nil? == false)
method = method.gsub!('_exsists','')
type = :check_if_exists
elsif (method.index('set_').nil? == false && params.count == 1)
method = method.gsub!('set_','')
type = :set
end
method = method.to_sym
if type == :get
return @store[method] if @store.keys.collect(&:to_sym).include?(method)
elsif type == :check_if_exists
return @store.keys.collect(&:to_sym).include?(method)
else
return @store[method] = params[0]
end
return nil # return nil if key does not exist
end
so who cares how it looks… for now
I care so I refactored it here.
Caveats :
1. Performance – obviously there is a performance penalty, I like not to dwell over it too much until it bites me. My experience has taught me that I am too stupid to catch performance issues (except the really obvious one) in advance. Knowing my limits I sit and wait for bottlenecks to introduce them self in a don’t-call-us-we’ll-call-you-manner (those guys really like to show off, don’t they)
2. Naming – if there is a hash item named same as existing method – you may not always get what you want. You can add prefix like this: store_price, store_amount… or you can just rename conflicting method
3. Encapsulation is broken – all of a sudden all hash elements are public even if a hash is a private member – on the bright side this can be fixed fairly easy