【问题标题】:Ruby - method_missingRuby - method_missing
【发布时间】:2012-03-27 22:33:49
【问题描述】:

我正在尝试实现一个 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:我希望你不要给我代码,而是一个解释,所以我可以自己确定它是如何完成的。

【问题讨论】:

  • 我正在解决同样的问题,有趣的是,这篇文章现在是谷歌“ruby method_missing”的前 10 名。

标签: ruby metaprogramming method-missing


【解决方案1】:

首先,安装我的单位库: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 >

【讨论】:

    【解决方案2】:

    这就是我所做的......

    http://pastebin.com/DpE8VAH4

    数字类 @@currencies = {'yen' => 0.013, '欧元' => 1.292, '卢比' => 0.019, '美元' => 1} def method_missing(方法,*arg) 奇异货币 = method.to_s.gsub(/s$/,'') 如果@@currencies.has_key?(singular_currency) 自我 * @@currencies[singular_currency] 别的 极好的 结尾 结尾 定义在(arg) 奇异货币 = arg.to_s.gsub(/s$/,'') 如果@@currencies.has_key?(singular_currency) 自我 * @@currencies[singular_currency] 结尾 结尾 结尾 放 "5.euro = "+5.euro.to_s 放 "5.euros = "+5.euros.to_s 把 "5.dollars.in(:euros) = "+5.dollars.in(:euros).to_s 把 "10.euros.in(:rupees) = "+10.euros.in(:rupees).to_s
    • 将“'dollar' => 1”添加到货币中
    • 在method_missing方法中添加一个新的参数“,*args”
    • 在 Numeric 类中添加新方法“in(arg)”
    • 此方法将 self 乘以参数“arg”指定的货币

    【讨论】:

      【解决方案3】:

      这更像是一个数学问题而不是计算问题。

      每个@@currencies 哈希值都被标准化为“美元”:它们的单位是日元/美元欧元/美元卢比/美元。对于5.euro.in(:yen),您只需将欧元/美元除以日元/美元即可将答案表示为日元欧元。

      要使用 Ruby 进行计算,请保持 method_missing 方法不变,并更新类常量以包含 'dollar' =&gt; 1。添加带有单行计算的Numeric#in 方法来解决此问题。该计算需要以正确的顺序将除法应用于浮点数。

      对于5.euro.in(:yen) 示例,请记住首先计算5.euro,但单位为欧元/美元。接下来的in(:yen) 方法必须应用于这个数字的倒数。这将给出一个以 yen/euro 为单位的数字,即您想要的结果的倒数。

      【讨论】:

        【解决方案4】:

        我也在做这门课程,我看到了一些如何完成任务的例子。在某些时候提到了 self.send,我相信其他人也已经实现了这一点,但我发现这个解决方案对我有用:

        https://gist.github.com/2065412

        【讨论】:

          【解决方案5】:

          我的解决方法,基于接受所提出问题的限制(在 Numeric 上扩展 method_missing 实现,尽管 @coreyward 表明这对于任何家庭作业问题来说确实是错误的方法) 如下:

          了解5.euros.in(:yen)可以翻译成:

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

          实际上发生的事情是,我们将欧元消息发送到 Numeric 5,然后将 in 方法发送到 5.euros 的 Numeric 结果,参数为 :yen。

          在 method_missing 中,您应该响应 euros 调用并返回欧元到美元的转换结果,然后(也在 method_missing 中)响应 in 调用并返回美元转换的结果(来自上一次调用)到作为参数传递给in 调用的符号。这将返回正确的值。

          当然,只要您的转换系数正确,您就可以转换为/从任何您想要的货币进行转换 - 考虑到这个特定问题,转换为美元/从美元转换似乎是最明智的。

          【讨论】:

            【解决方案6】:

            添加货币“美元”和in方法:

            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
            

            【讨论】:

              【解决方案7】:

              也许这会更有帮助。这是一个工作示例(注意,我希望您拥有 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
              

              【讨论】:

                【解决方案8】:

                与其在此处使用method_missing 相比,迭代每种货币并为它们定义单数和复数方法以委托给您的转换方法会更容易。

                为了方便起见,我假设您在这里有 ActiveSupport。你可以不这样做,但像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
                

                【讨论】:

                • 阅读我离开戴夫的评论。
                • 不,作业是学习 Ruby 元编程和 method_missing 是如何工作的,他们选择这样做是无关紧要的。
                • 我在这里演示的是性能更高、不言自明的元编程。这不是学习如何使用method_missing 的好例子,除非您正在动态查找货币换算表。如果您想真正了解 Ruby 元编程,请获取一份 Metaprogramming Ruby by Paolo Perrotta 的副本。
                • 谢谢coreyward,我理解你的观点,但这只是一个作业,并不代表我对什么是对什么是错的看法,我只是在努力学习。不管你对这个问题的立场是什么,现在你能帮我理解如何做我的要求吗?
                • 当然不一定要method_missing,我该怎么做 Numeric.dollars.in(:euro)
                【解决方案9】:

                您不只定义一个名为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 假设一切都是美元,即使不是。

                这就是为什么会有money gem :)

                【讨论】:

                • 这是斯坦福 SaaS 课程的家庭作业,这就是我学习的原因,您能详细说明一下吗?
                • @8vius 5.euro 返回一个数字,但返回值只是另一个FixNum;你无法知道它代表什么货币。如果您假设每个数字都代表美元,那么我将符号发送回数字的想法就失败了,因为这意味着 method_missing 正在尝试做某事。所以你可以做一些更接近其他答案建议的事情,in 函数可以对符号进行简单的查找以获得转换因子。
                猜你喜欢
                • 2010-09-22
                • 1970-01-01
                • 2011-10-05
                • 2013-09-24
                • 2011-10-20
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多