为什么我不能在模块中扩展 Fixnum class 并使用它?

Why can't I extend the Fixnum class in a module and use it?

我创建了一个模块,在其中我使用新方法扩展了 Fixnum class。但是当我 require 模块并尝试使用扩展方法时,它 returns:

NoMethodError: undefined method `roundup' for 13:Fixnum

这是我的模块的样子:

module EanControl
  # Extend Fixnum with #roundup
  class Fixnum
    def self.roundup
      return self if self % 10 == 0   # already a factor of 10
      return self + 10 - (self % 10)  # go to nearest factor 10
    end
  end

  # More code...
end

这就是我正在做的事情:

require 'path_to_module'
12.roundup

# => NoMethodError: undefined method `roundup' for 13:Fixnum

我该如何解决这个问题?

在您的扩展中,您已将 roundup 定义为 class 方法,而 12 实例.

尝试:

module EanControl
  # Extend Fixnum with #roundup
  class Fixnum
    def roundup
      return self if self % 10 == 0   # already a factor of 10
      return self + 10 - (self % 10)  # go to nearest factor 10
    end
  end

  # More code...
end

您的代码存在三个问题:

  1. 您正在创建 新的 class EanControl::Fixnum,但您实际上想要更改 现有的 内置 ::Fixnum。解决方案:显式地从顶层开始常量查找,或者更通俗地说,只是删除模块。

    module EanControl
      class ::Fixnum
        # …
      end
    end
    
    # although it would be much simpler to just do this:
    
    class Fixnum
      # …
    end
    
  2. 您将roundup定义为对象Fixnum的单例方法,但您将其调用为Fixnum实例的实例方法。解决方案:使roundup成为一个实例方法:

    class Fixnum
      def roundup
        return self if (self % 10).zero? # already a factor of 10
        self + 10 - (self % 10)          # go to nearest factor 10
      end
    end
    
  3. Ruby 语言规范实际上并不能保证 一个 Fixnum class。它只保证有一个 Integer class,并且它允许不同的实现可以提供特定于实现的 subclasses。 (例如,YARV 有 IntegerFixnumBignum subclasses。)由于您只将方法添加到 Fixnum,因此它不适用于其他方法Integer 不是 Fixnum。并且由于 Fixnums 的范围对于不同的架构实现是不同的(例如,在 32 位系统的 YARV 上,Fixnums 是 31 位,在 64 位系统上,它们是 63 位,在 JRuby,它们始终是 64 位),您甚至不确定您的方法将适用于哪些数字以及何时会失败。 (例如:9223372036854775808.roundup # NoMethodError: undefined method 'roundup' for 9223372036854775808:Bignum。) 解决方法:使该方法成为Integer:

    的实例方法
    class Integer
      def roundup
        return self if (self % 10).zero? # already a factor of 10
        self + 10 - (self % 10)          # go to nearest factor 10
      end
    end
    

最后,我想建议至少在这里使用 mixin:

module IntegerWithRoundup
  def roundup
    return self if (self % 10).zero? # already a factor of 10
    self + 10 - (self % 10)          # go to nearest factor 10
  end
end

class Integer
  include IntegerWithRoundup
end

现在,如果其他人调试了您的代码,并且想知道此 roundup 方法来自何处,则祖先链中有清晰的痕迹:

12.method(:roundup).owner
# => IntegerWithRoundup

更好的方法是使用优化,这样你的 monkeypatch 就不会污染全局命名空间:

module IntegerWithRoundup
  module Roundup
    def roundup
      return self if (self % 10).zero? # already a factor of 10
      self + 10 - (self % 10)          # go to nearest factor 10
    end
  end

  refine Integer do
    include Roundup
  end
end

12.roundup
# NoMethodError: undefined method `roundup' for 12:Fixnum

using IntegerWithRoundup

12.roundup
# => 20