I've created a module in which I extend the Fixnum class with a new method. But when I require
the module and try to use the extended method, it returns:
NoMethodError: undefined method `roundup' for 13:Fixnum
Here's what my module looks like:
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
This is what I'm doing:
require 'path_to_module'
12.roundup
# => NoMethodError: undefined method `roundup' for 13:Fixnum
How could I solve this?
There are three problems with your code:
You are creating a new class EanControl::Fixnum
, but you actually want to change the existing builtin ::Fixnum
. Solution: explicitly start the constant lookup from the top-level, or, more idiomatically, just drop the module.
module EanControl
class ::Fixnum
# …
end
end
# although it would be much simpler to just do this:
class Fixnum
# …
end
You define roundup
as a singleton method of the object Fixnum
, but you call it as an instance method of instances of Fixnum
. Solution: make roundup
an instance method:
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
The Ruby Language Specification does not actually guarantee that there even is a Fixnum
class. It only guarantees that there is an Integer
class, and it allows that different implementations may provide implementation-specific subclasses. (E.g. YARV has Fixnum
and Bignum
subclasses of Integer
.) Since you only add the method to Fixnum
, it won't work for other Integer
s, which aren't Fixnum
s. And since the range of Fixnum
s is different for different implementations of architectures (e.g. on YARV on 32 bit systems, Fixnum
s are 31 bit, on 64 bit systems, they are 63 bit, on JRuby, they are always 64 bit), you don't even know for sure what numbers your method will work on and when it will fail. (E.g.: 9223372036854775808.roundup # NoMethodError: undefined method 'roundup' for 9223372036854775808:Bignum
.) Solution: make the method an instance method of 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
Lastly, I want to suggest at least using a mixin here:
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
Now, if someone else debugs your code, and wonders where this roundup
method comes from, there is a clear trace in the ancestry chain:
12.method(:roundup).owner
# => IntegerWithRoundup
Even better would be to use a Refinement, that way your monkeypatch doesn't pollute the global namespace:
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