I'm trying to implement a method_missing for converting $ to other currencies, as in doing 5.dollars yields 5, 5.yen would yield 0.065 5.euro 6.56 and so on. This I can do now. Now I need to implement it but doing 5.dollars.in(:yen) for example.
This is what I have right now:
class Numeric
@@currencies = {'yen' => 0.013, 'euro' => 1.292, 'rupee' => 0.019}
def method_missing(method_id)
singular_currency = method_id.to_s.gsub( /s$/, '')
if @@currencies.has_key?(singular_currency)
self * @@currencies[singular_currency]
else
super
end
end
end
Can anyone explain how I can do this?
PS: I'd rather you not give me the code, but an explanation, so I can determine on my own how it is done.
Perhaps this will be of more help. It's a working example (note, I'm expecting you to have ActiveSupport [part of Rails] and Ruby 1.9.2+):
require 'rubygems'
# This is allowing us to do the `pluralize` calls below
require 'active_support/inflector'
module Currency
CONVERSION_TABLE = { dollars: { dollars: 1, euros: 0.75 }, euros: { dollars: 1.3333334, euros: 1 } }.freeze
attr_accessor :currency
def method_missing(method_name, *args, &block)
# standardize on pluralized currency names internally so both singular
# and plural methods are handled
method_name = method_name.to_s.pluralize.to_sym
# Use the "from" keys in the conversion table to verify this is a valid
# source currency
if CONVERSION_TABLE.key?(method_name)
@currency = method_name
self # return self so a call to `1.dollar` returns `1` and not `:dollars`
else
super
end
end
# Convert `self` from type of `@currency` to type of `destination_currency`, mark the result with
# the appropriate currency type, and return. Example:
def to(destination_currency)
# Again, standardize on plural currency names internally
destination_currency = destination_currency.to_s.pluralize.to_sym
# Do some sanity checking
raise UnspecifiedSourceCurrency unless defined?(@currency)
raise UnsupportedDestinationCurrency unless CONVERSION_TABLE.key?(destination_currency)
# Do the actual conversion, and round for sanity, though a better
# option would be to use BigDecimal which is more suited to handling money
result = (self * CONVERSION_TABLE[@currency][destination_currency]).round(2)
# note that this is setting @currency through the accessor that
# was created by calling `attr_accessor :currency` above
result.currency = destination_currency
result
end
end
class Numeric
# Take all the functionality from Currency and mix it into Numeric
#
# Normally this would help us encapsulate, but right now it's just making
# for cleaner reading. My original example contained more encapsulation
# that avoided littering the Numeric clas, but it's harder for a beginner
# to understand. For now, just start here and you will learn more later.
include Currency
end
p 5.euros.to(:dollars) #=> 6.67
p 0.25.dollars.to(:euro) #=> 0.19
p 1.dollar.to(:euros).to(:dollar) #=> 1.0