Ruby - method_missing

I am trying to implement method_missing to convert $ to other currencies, since doing 5.dollars yields 5, 5.yen will give 0.065 5.euro 6.56 and so on. I can do this now. Now I need to implement it, but for example do 5.dollars.in (: yen).

Here's 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 someone explain how I can do this?

PS: I would prefer you not to give me the code, but an explanation, so I can figure out how it's done myself.

+3


source to share


9 replies


Maybe this will help. This is a working example (note, I expect 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 just making
  # for cleaner reading. My original example contained more encapsulation
  # that avoided littering the Numeric clas, but it 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

      

+4


source


Added currency "dollar" to :



class Numeric
  @@currencies = {'dollar' => 1, '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

  def in(currency)
    singular_currency = currency.to_s.gsub(/s$/, '')
    self / @@currencies[singular_currency]
  end
end

      

+9


source


This is more of a mathematical problem than a computational one.

Each of the hash values ​​is @@currencies

normalized to "dollars": their units are yen / dollar, euro / dollar, rupee / dollar. For 5.euro.in(:yen)

you need to divide euro / dollar by yen / dollar to express the answer as euro in yen.

To calculate this using Ruby, you leave the method method_missing

unchanged and update the class constant to include 'dollar' => 1

. Add a Numeric#in

one-line evaluation method to solve this problem. This calculation must apply division in the correct order to a floating point number.

In case, 5.euro.in(:yen)

remember that 5.euro is calculated first, but will have units of euro / dollar. The method to be applied (: yen), which is next, must be applied to the reciprocal of this number. This will give the yen / euro units that match your desired outcome.

+3


source


Didn't you just define a named method in

that sent the symbol parameter back to self

?

irb(main):057:0> 5.dollar.in(:euro)
=> 6.46
irb(main):065:0> 5.euro.in(:dollar)
=> 6.46 # Which is wrong, by the way

      

So, not entirely true, because you don't know what the amount is currently - yours method_missing

assumes everything is in dollars, even if it isn't.

That's why the money stone :)

+2


source


Instead of using it method_missing

here, it would be easier to iterate over each of the currencies and define single and multiple methods to delegate them to your conversion method.

I am assuming you have ActiveSupport for convenience. You can do any of this without, but things like constantize

problems make it easier.

module DavesMoney
  class BaseMoney
    # your implementation
  end

  class DollarConverter < BaseMoney
    def initialize(value)
      @value = value
    end

    def to(:currency)
      # implemented in `BaseMoney` that gets extended (or included)
    end
  end
end

module CurrencyExtension
  extend ActiveSupport::Concern

  SUPPORTED_CURRENCIES = %w{ dollar yen euro rupee }

  included do
    SUPPORTED_CURRENCIES.each do |currency|
      define_method :"#{currency}" do
        return "#{currency}_converter".constantize.new(self)
      end
      alias :"#{currency.pluralize}" :"#{currency}"
    end
  end
end

# extension
class Numeric
  include CurrencyExtension
end

      

+1


source


My approach to this, based on recognizing the limits of the task at hand (extending the implementation of method_missing to Numeric, although as @coreyward points out this is indeed the wrong approach for anything other than a home problem) was as follows:

Understanding what 5.euros.in(:yen)

can be translated into:

eur = 5.send(:euros)
eur.send( :in, yen )

      

what basically happens is that we send the message euros to the number 5 and then send the method in

to the numerical result 5.euros with the parameter: yen.

In method_missing, you must answer the call euros

and return with the result of converting euros to dollars, and then (also in method_missing) answer the call in

with the results of converting dollars (from the previous call) to the character passed as a parameter to the call in

. This will return the correct value.

Of course, you can convert to / from any currency you want as long as your conversion rates are correct - with gifts for this particular problem, converting to / from dollars seemed the most reasonable.

+1


source


I am doing this course too and I saw some examples of how to complete the task. Self.send was mentioned at some point and I believe someone else has implemented this, but I found this solution to work for me:

https://gist.github.com/2065412

+1


source


Here's what I did ...

http://pastebin.com/DpE8VAH4

    class Numeric
      @@ currencies = {'yen' => 0.013, 'euro' => 1.292, 'rupee' => 0.019, 'dollar' => 1}
      def method_missing (method, * arg)
        singular_currency = method.to_s.gsub (/ s $ /, '')
        if @@ currencies.has_key? (singular_currency)
          self * @@ currencies [singular_currency]
        else
          super
        end
      end
      def in (arg)
        singular_currency = arg.to_s.gsub (/ s $ /, '')
        if @@ currencies.has_key? (singular_currency)
          self * @@ currencies [singular_currency]
        end
      end
    end

    puts "5.euro =" + 5.euro.to_s
    puts "5.euros =" + 5.euros.to_s
    puts "5.dollars.in (: euros) =" + 5.dollars.in (: euros) .to_s
    puts "10.euros.in (: rupees) =" + 10.euros.in (: rupees) .to_s
  • Add "dollar" => 1 "to currencies
  • Add new method argument method_missing ", * args"
  • Add new method "in (arg)" to numeric class
  • This method multiplies self by the currency specified by the "arg" argument
0


source


First, install the units library: gem install sy

. Then define:

require 'sy'
Money = SY::Quantity.dimensionless      #=> #<Quantity:Money>
USD = SY::Unit.standard of: Money       #=> #<Unit:USD of Money >
YEN = SY::Unit.of Money, amount: 0.013  #=> #<Unit:YEN of Money >
EUR = SY::Unit.of Money, amount: 1.292  #=> #<Unit:EUR of Money >
INR = SY::Unit.of Money, amount: 0.019  #=> #<Unit:INR of Money >

      

And now you can calculate:

10 * 10.usd => #<Magnitude: 100 >
100.yen.in :usd #=> #<Magnitude: 1.3 >
1.eur + 1.usd #=> #<Magnitude: 2.29 >

      

You can also define

CENT = SY::Unit.of Money, amount: 0.01.usd
EUROCENT = SY::Unit.of Money, amount: 0.01.eur

      

And then

12.usd + 90.cent #=> #<Magnitude: 12.9 >

      

0


source







All Articles