I want to prepend Kernel.rand
like this:
# I try something like
mod = Module.new do
def rand(*args)
p "do something"
super(*args)
end
end
Kernel.prepend(mod)
# And I expect this behaviour
Kernel.rand #> prints "do something" and returns random number
rand #> prints "do something" and returns random number
Object.new.send(:rand) #> prints "do something" and returns random number
Unfortunately, the code above does not work as I want to. Prepending Kernel.singleton_class
does not work too
It's not required to use prepend
feature, any suggestiion that will help to achieve the desired behaviour is welcome
Kernel
methods like rand
or Math
methods like cos
are defined as so-called module functions (see module_function
) which makes them available as both,
... (public) singleton methods:
Math.cos(0) # <- `cos' called as singleton method
#=> 1.0
... and (private) instance methods:
class Foo
include Math
def calc
cos(0) # <- `cos' called from included module
end
end
foo = Foo.new
foo.calc
#=> 1.0
foo.cos(0) # <- not allowed
# NoMethodError: private method `cos' called for #<Foo:0x000000010e3ab510>
To achieve this, Math
's singleton class doesn't simply include Math
(which would turn all its methods into singleton methods). Instead, each "module function" method gets defined twice, in the module and in the module's singleton class:
Math.private_instance_methods(false)
#=> [:ldexp, :hypot, :erf, :erfc, :gamma, :lgamma, :sqrt, :atan2, :cos, ...]
# ^^^
Math.singleton_class.public_instance_methods(false)
#=> [:ldexp, :hypot, :erf, :erfc, :gamma, :lgamma, :sqrt, :atan2, :cos, ...]
# ^^^
As a result, prepending another module to Math
or patching Math
in general will only affect the (private) instance method and thus only classes including Math
. It won't affect the cos
method which was defined separately in Math
's singleton class. To also patch that method, you'd have to prepend your module to the singleton class, too:
module MathPatch
def cos(x)
p 'cos called'
super
end
end
Math.prepend(MathPatch) # <- patch classes including Math
Math.singleton_class.prepend(MathPatch) # <- patch Math.cos itself
Which gives:
Math.cos(0)
# "cos called"
#=> 1.0
As well as:
foo.calc
# "cos called"
#=> 1.0
However, as a side effect, it also makes the instance method public:
foo.cos(0)
# "cos called"
#=> 1.0
I've picked Math
as an example because it's less integrated than Kernel
but the same rules apply to "global functions" from Kernel
.
What's special about Kernel
is that it's also included into main
which is Ruby's default execution context, i.e. you can call rand
without an explicit receiver.