Ruby - method_missing - PullRequest
       59

Ruby - method_missing

3 голосов
/ 11 марта 2012

Я пытаюсь реализовать метод method_missing для конвертации $ в другие валюты, так как при выполнении 5.dollars получается 5, 5.yen - 0.065 5.euro 6.56 и так далее. Это я могу сделать сейчас. Теперь мне нужно реализовать это, но, например, сделать 5.dollars.in (: yen).

Это то, что у меня сейчас есть:

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

Может кто-нибудь объяснить, как я могу это сделать?

PS: Я бы предпочел, чтобы вы дали мне не код, а объяснение, чтобы я сам мог определить, как это делается.

Ответы [ 9 ]

10 голосов
/ 13 марта 2012

Добавлена ​​валюта 'доллар' и в метод:

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
4 голосов
/ 11 марта 2012

Возможно, это поможет вам.Это рабочий пример (обратите внимание, я ожидаю, что у вас есть ActiveSupport [часть Rails] и 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
3 голосов
/ 20 марта 2012

Это скорее математическая проблема, чем вычислительная.

Каждое из значений хеша @@currencies нормировано на «доллары»: их единицы: иена / доллар , евро / доллар , рупия / доллар.Для 5.euro.in(:yen) вам нужно только разделить евро / доллар на иена / доллар , чтобы выразить ответ в евро в иенах.

Чтобы вычислить это с помощью Ruby,вы оставляете метод method_missing без изменений и обновляете константу класса, чтобы включить 'dollar' => 1.Для решения этой проблемы добавьте метод Numeric#in с вычислением в одну строку.Это вычисление должно применить деление в правильной последовательности к числу с плавающей точкой.

Для примера 5.euro.in(:yen), помните, что 5.euro вычисляется первым, но будет иметь единицы измерения евро / доллар .Следующий метод in (: yen) должен применяться к обратной величине этого числа.Это даст число с единицами в иен / евро , обратное вашему желаемому результату.

2 голосов
/ 11 марта 2012

Не могли бы вы просто определить метод с именем in, который отправил бы параметр символа обратно в 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

Итак, не совсем, потому что вы не знаете, что представляет сумма на данный момент - ваш method_missing предполагает, что все в долларах, даже если это не так.

Вот почему есть денежный камень :)

1 голос
/ 18 марта 2012

Я тоже прохожу этот курс и видел несколько примеров того, как выполнить задачу.В какой-то момент было упомянуто self.send, и я полагаю, что кто-то еще реализовал это, но я нашел, что это решение работает для меня:

https://gist.github.com/2065412

1 голос
/ 15 марта 2012

Мой подход к этому основан на принятии ограничений поставленной проблемы (расширить реализацию method_missing на Numeric, даже если @coreyward указывает, что это действительно неправильный подход для всего, что не является проблемой домашней работы) было следующим:

Понимание того, что 5.euros.in(:yen) можно перевести на:

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

в сущности происходит то, что мы отправляем сообщение евро в Числовой 5, а затем отправляем метод in для числового результата 5.euros с параметром: иена.

В method_missing вы должны ответить на вызов euros и вернуться с результатом конвертации евро в доллары, а затем (также в method_missing) ответить на вызов in с результатами конвертации долларов (из предыдущий вызов) для символа, переданного в качестве параметра для вызова in. Это вернет правильное значение.

Конечно, вы можете конвертировать в / из любой валюты, которую хотите, при условии, что ваши коэффициенты конвертации верны - с учетом данной конкретной проблемы конвертация в / из долларов казалась наиболее разумной.

1 голос
/ 11 марта 2012

Вместо использования method_missing здесь было бы проще выполнить итерацию по каждой из валют и определить методы единственного и множественного числа для их делегирования в ваш метод преобразования.для удобства.Вы можете сделать все это без, но такие вещи, как constantize и проблемы, облегчают это.

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
0 голосов
/ 13 июня 2013

Сначала установите библиотеку моих модулей: gem install sy.Затем определите:

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 >

И теперь вы можете вычислить:

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

Вы также можете определить

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

И затем

12.usd + 90.cent #=> #<Magnitude: 12.9 >
0 голосов
/ 30 мая 2012

Вот что я сделал ...

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
  • Добавить "доллар" => 1 "в валютах
  • Добавить новый аргумент в метод method_missing ", * args"
  • Добавить новый метод "in (arg)" в числовой класс
  • Этот метод умножает себя на валюту, указанную аргументом "arg"
...