I extended Kernel
by itself, and within the definition of the instance method Kernel#abort
, I called the singleton method Kernel.abort
:
module Kernel
extend self
def abort
puts "Press ENTER to exit..."
gets
Kernel.abort
end
end
abort
When I call Kernel#abort
, it seems that the Kernel.abort
call inside the method definition refers to the original Kernel#abort
(extended as Kernel.abort
).
How does Ruby know that when I write Kernel.abort
, I mean the original abort
method, not the one I just created? How would I recursively call the new abort
method I just created?
Kernel.abort
is defined by first defining an instance method Kernel#abort
and then making it also a singleton method with module_function
. (This is definitely the case in Rubinius; I couldn't find it in the MRI source but see below.) module_function
makes a copy of the method. When you redefine abort
you redefine the instance method but not the singleton copy.
Object
includes Kernel
, so when you say abort
you get the instance method, which you've redefined, but when you say Kernel.abort
you get the singleton method, which you haven't redefined.
If you really wanted to use recursion in abort
, or just to demonstrate that this explanation is correct, call module_function :abort
after redefining the method. The singleton method will be updated to be the same as the instance method and both methods will recurse.
Note that you didn't need to extend self
to redefine the instance version of abort
. Since Kernel
is already included in Object
, you only needed to redefine the instance method for all objects to see the redefined version. On the other hand, if Kernel
had used extend self
to expose #abort
in the first place, we could redefine it without any complications.
The following demonstrates that the lack of recursion happens with user-defined, pure Ruby methods, i.e. that module_function
is responsible and native methods are not:
$ cat foo.rb
module Foo
def bar
puts "old version"
end
module_function :bar
end
module Foo
def bar
puts "new version"
Foo.bar
end
end
Object.include Foo
bar
$ ruby foo.rb
new version
old version
You should be doing something like this:
module Kernel
class << self
alias :real_abort :abort
def abort
puts "press enter"
gets
puts "invoking real abort"
real_abort
end
end
end
The reason IRB is invoking the original abort and not your defined abort
is because the IRB repl has a abort method thats a C-language variant of it.
You can see it by doing show-source
on pry
[1] pry(main)> show-source abort
From: process.c (C Method):
Owner: Kernel
Visibility: private
Number of lines: 22
VALUE
rb_f_abort(int argc, const VALUE *argv)
{
rb_check_arity(argc, 0, 1);
if (argc == 0) {
if (!NIL_P(GET_THREAD()->errinfo)) {
ruby_error_print();
}
rb_exit(EXIT_FAILURE);
}
else {
VALUE args[2];
args[1] = args[0] = argv[0];
StringValue(args[0]);
rb_io_puts(1, args, rb_stderr);
args[0] = INT2NUM(EXIT_FAILURE);
rb_exc_raise(rb_class_new_instance(2, args, rb_eSystemExit));
}
UNREACHABLE;
}
In the code you mentioned the abort thats being executed is not Kernel.abort but the C-language abort thats mapped to IRB when you invoke it from commandline
If you want it to be masked you could do something like this so that the abort
in IRB repl would execute your redefined abort
def abort; Kernel.abort; end
Then if you run abort
it would invoke your redefined Kernel's abort singleton method.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With