Ruby, adding methods to a Module, Class or Object - ruby

I noticed something odd while I was adding methods to Kernel to make them available globally. It's interesting, and I'm looking for some documentation or good explanation.
Let's look at the code:
file: ./demo.rb
# example 1
module Kernel
def foo
puts "I'm defined inside the module!"
end
end
# example 2
module Bar
def bar
puts "I'm included! (bar)"
end
end
Kernel.send :include, Bar
# example 3
module Baz
def baz
puts "I'm included! (baz)"
end
end
module Kernel
include Baz
end
Then, in bash and IRB
$ irb -r ./demo.rb
> foo
# I'm defined inside the module!
> bar
# NameError: undefined local variable or method `bar' for main:Object
> baz
# NameError: undefined local variable or method `baz' for main:Object
>
> self.class.ancestors
# => [Object, Kernel, BasicObject]
>
> include Kernel
>
> self.class.ancestors
# => [Object, Kernel, Baz, Bar, BasicObject]
>
> foo
# I'm defined inside the module!
> bar
# I'm included! (bar)
> baz
# I'm included! (baz)
foo works as expected, and is available for all objects that include Kernel. bar and baz, on the other hand, are not immediately available.
I imagine it's because the evaluation context of IRB (an Object) already includes Kernel, and including a module A inside a module B will not "reload" all previous inclusions of B.
Ok, it makes perfect sense, and in fact re-including Kernel will add the other two methods.
Then, my questions are:
Why does opening Kernel work? (example 1)
if opening the module is treated differently, why isn't the 3rd example working as well?

What happens you call foo.bar in Ruby? Something like this:
foo.class.ancestors.each do |klass|
if klass.public_instance_methods.include? :bar
return klass.instance_method(:bar).bind(foo).call
end
end
raise NameError
i.e. Ruby searches through the ancestors to find a matching instance method.
And what happens when you call A.include B in Ruby? Something like this:
B.ancestors.each do |mod|
A.ancestors << mod unless A.ancestors.include? mod
end
B and all of its ancestors become ancestors of A. These two behaviors explain everything:
Opening Kernel works because it's included in Object and is thus an ancestor of every object, meaning its methods (including new ones) can be searched whenever you call a method on any object.
Opening a module isn't treated differently. Your second and third examples are effectively the same. They both don't work because Kernel's ancestors are only searched when it is included, which was before you added new ancestors to it.

Related

Include a Ruby module but change the root module reference

Given a Module Foo, I would like to extend this module, but not preserve the module name Foo.
# sscce (irb)
module Foo
class Foo; end
end
module Bar
include Foo
end
> Foo::Foo
Foo::Foo
> Bar.ancestors
[Bar, Foo]
> Bar::Foo
Foo::Foo
How can I get Bar::Foo to be Bar::Foo (not Foo::Foo) without redefining the class?
I've attempted include and prepend as well as include Foo.clone with the same result. I think it's safe to assume that it's referencing the original class definition, fine, but I'd still like to have the class Foo actually belong to Bar.
maybe something like this could work?
module Foo
class Foo
def foo
puts "inside: #{self.class}"
end
end
end
module Bar
class Foo < ::Foo::Foo; end
end
now when you do:
a = Foo::Foo.new
a.foo # inside: Foo::Foo
and if you do
b = Bar::Foo.new
b.foo # inside: Bar::Foo
instead of trying to include/extend Foo in a tricky way just create a new class inside the Bar module and rely on inheritance?
You cannot make a class belong to a module by inclusion. Even more - a class doesn't belong to a module even without inclusion (only a reference to a class is available as a module constant). When you import Foo to Bar, you bring there the constants and instance methods from Foo, that's why you have access to Foo::Foo by using the Bar::Foo constant and it points to the uniquely identified class Foo::Foo.
If you want to have the class Bar::Foo, it will be a different class uniquely identified by this "address". And if you want to have it identical to Foo::Foo, you'll have to use the included hook in module Foo and then do some heavy metaprogramming stuff to create a new class from the old one and assign it to a constant with the same name from the new module.
Or, maybe it could be enough just to clone the class:
module Foo
class Foo; end
def included(mod)
mod.const_set('Foo', self::Foo.clone)
end
end
module Bar
include ::Foo
end
obj1 = Foo::Foo.new
obj2 = Bar::Foo.new
Foo::Foo == Bar::Foo # false
But I'm not sure if cloning is enough for all the cases.

Module classes not visible in Minitest describe

When I want to include a module into a Minitest/spec test, I can access the functions from the module, but not the classes defined in it. Example:
module Foo
def do_stuff
end
class Bar
end
end
x=describe Foo do
include Foo
end
p x.constants # shows :Bar
describe Foo do
include Foo
it "foos" do
do_stuff # works
Bar.new # raises a NameError
end
end
Running this snippet gives me a "NameError: uninitialized constant Bar", however, the p x.constantsshows that Bar is defined. I looked into the Minitest source code for describe and it uses class_eval on the block in the context of some anonymous class. When I do that in the context of a normal class it works fine and I can access Bar. Why doesn't it work with describe/it or what do I have to do in order to access the classes directly?
EDIT:
Interestingly, if you call class_eval directly on some class the included class Bar can be found, e.g.
class Quux
def it_foos
do_stuff # works
Bar.new # does NOT raise a NameError
end
end
Quux.class_eval do
include Foo
end
Quux.new.it_foos
won't throw a NameError...
If you check the documentation for #class_eval (for example, https://ruby-doc.org/core-2.5.0/Module.html#method-i-class_eval) you will see the answer there: "Evaluates the string or block in the context of mod, except that when a block is given, constant/class variable lookup is not affected".
So, include within class_eval simply doesn't affect constants resolution.
As far as I understand from the short look at minitest's source code, describe internally creates a new anonymous class (let's name it C) and casts class_eval on it with the block you provide. During this call its create the respective test instance methods that are executed later. But include doesn't affect constants resolution for C, so Bar stays unknown.
There is an obvious (and quite ugly) solution - the following should work because you include Foo into outer context, so Bar goes into lexical scope accessible for describe:
include Foo
describe Foo do
it "foos" do
do_stuff
Bar.new
end
end
But tbh I'd avoid such code. Probably it's better to set up the class mock explicitly, smth like
module Foo
def do_stuff
"foo"
end
class Bar
def do_stuff
"bar"
end
end
end
...
describe Foo do
let(:cls) { Class.new }
before { cls.include(Foo) }
it "foos" do
assert cls.new.do_stuff == "foo"
end
it "bars" do
assert cls::Bar.new.do_stuff == "bar"
end
end
(but take pls the latter with a grain of salt - I almost never use Minitest so have no idea of its "common idioms")

Find a constant (a class) scoped to a specific namespace (a module)

I'm trying to programmatically fetch a constant (e.g. a class), but want to only look at the constants defined in a specific namespace. However, const_get will bubble up to higher namespaces while searching for the constant. For instance:
module Foo
class Bar
end
end
class Quux
end
If you then ask Foo to return the constant "Bar", it'll return the correct class.
Foo.const_get('Bar')
#=> Foo::Bar
However, if you ask it for "Quux", it'll bubble up it's search path and find the top-level Quux:
Foo.const_get('Quux')
#=> Quux
Is there any way to make it only search in the module that const_get is called on?
Module#const_get says:
Checks for a constant with the given name in mod If inherit is set, the lookup will also search the ancestors (and Object if mod is a Module.)
The value of the constant is returned if a definition is found, otherwise a NameError is raised.
You can then do as below:
module Foo
class Bar
end
end
class Quux
end
Foo.const_get('Quux',false) rescue NameError
# >> NameError
Foo.const_get('Bar',false) rescue NameError
# >> Foo::Bar

Why is "hello" printed twice in this Ruby script?

I decided to experiment with defining methods inside methods in Ruby and wrote this:
def foo
def bar
puts "hello"
end
bar
end
This defined, I ran foo and "hello" was printed as I expected. However I then tried foo.bar - which printed out "hello" twice. Why?
foo.bar equals foo().bar() so you call first foo which contains bar so it is executed once here, then bar is executed one more time. At the end bar is called twice.
def foo
puts 'foo called'
def bar
puts 'bar called'
puts "hello"
end
bar
end
foo.bar
#=> foo called
#=> bar called
#=> hello
#=> bar called
#=> hello
What I don't understand is this means the result of foo which is nil can call bar.
>> nil.bar
#=> hello
How is that possible ?
Edit: As explained in some answers, nested method are included in the class scope so weither bar is declared inside or outside foo does not make any difference.
Also Matz said that it may change from Ruby2.0
here is the example he wrote:
class Foo
def foo
def bar
#omited
end
bar # can be called
end
def quux
bar # cannot be called
end
end
edit-Ruby2.0: It finally did not get implemented. So it remains the same.
In Ruby, there is a hidden variable that doesn't get much attention. I don't know what it's called, but I call it the current class. This variable is the target of def
When you go into foo, it doesn't change this variable, you're still in the same class. So when you define another method inside of foo, that method gets defined on the same class as foo was defined on.
In this case, you're defining foo on main, the toplevel object. Main's current class variable is set to object, so any methods you define here will be available inside any object.
Since the last thing you do is return the result of bar, and bar returns nil (b/c puts returns nil), then foo.bar is nil.bar. And since you defined bar on Object, even nil inherits this method.
The interesting part is that bar is public. Since foo is defined in main, and main sets its scope to private, I would have expected bar to also be private, but I guess that state must lost once you go to a different scope.
Did you mean to put class foo? It seems that calling foo.bar calls the function foo and then the function bar. Calling nil.bar prints out 'hello' too.

Is it possible to give a sub-module the same name as a top-level class?

Background:
ruby thinks I'm referencing a top-level constant even when I specify the full namespace
How do I refer to a submodule's "full path" in ruby?
Here's the problem, distilled down to a minimal example:
# bar.rb
class Bar
end
# foo/bar.rb
module Foo::Bar
end
# foo.rb
class Foo
include Foo::Bar
end
# runner.rb
require 'bar'
require 'foo'
➔ ruby runner.rb
./foo.rb:2: warning: toplevel constant Bar referenced by Foo::Bar
./foo.rb:2:in `include': wrong argument type Class (expected Module) (TypeError)
from ./foo.rb:2
from runner.rb:2:in `require'
from runner.rb:2
Excellent; your code sample is very clarifying. What you have there is a garden-variety circular dependency, obscured by the peculiarities of Ruby's scope-resolution operator.
When you run the Ruby code require 'foo', ruby finds foo.rb and executes it, and then finds foo/bar.rb and executes that. So when Ruby encounters your Foo class and executes include Foo::Bar, it looks for a constant named Bar in the class Foo, because that's what Foo::Bar denotes. When it fails to find one, it searches other enclosing scopes for constants named Bar, and eventually finds it at the top level. But that Bar is a class, and so can't be included.
Even if you could persuade require to run foo/bar.rb before foo.rb, it wouldn't help; module Foo::Bar means "find the constant Foo, and if it's a class or a module, start defining a module within it called Bar". Foo won't have been created yet, so the require will still fail.
Renaming Foo::Bar to Foo::UserBar won't help either, since the name clash isn't ultimately at fault.
So what can you do? At a high level, you have to break the cycle somehow. Simplest is to define Foo in two parts, like so:
# bar.rb
class Bar
A = 4
end
# foo.rb
class Foo
# Stuff that doesn't depend on Foo::Bar goes here.
end
# foo/bar.rb
module Foo::Bar
A = 5
end
class Foo # Yep, we re-open class Foo inside foo/bar.rb
include Bar # Note that you don't need Foo:: as we automatically search Foo first.
end
Bar::A # => 4
Foo::Bar::A # => 5
Hope this helps.
Here is a more minimal example to demonstrate this behavior:
class Bar; end
class Foo
include Foo::Bar
end
Output:
warning: toplevel constant Bar referenced by Foo::Bar
TypeError: wrong argument type Class (expected Module)
And here is even more minimal:
Bar = 0
class Foo; end
Foo::Bar
Output:
warning: toplevel constant Bar referenced by Foo::Bar
The explanation is simple, there is no bug: there is no Bar in Foo, and Foo::Bar is not yet defined. For Foo::Bar to be defined, Foo has to be defined first. The following code works fine:
class Bar; end
class Foo
module ::Foo::Bar; end
include Foo::Bar
end
However, there is something that is unexpected to me. The following two blocks behave differently:
Bar = 0
class Foo; end
Foo::Bar
produces a warning:
warning: toplevel constant Bar referenced by Foo::Bar
but
Bar = 0
module Foo; end
Foo::Bar
produces an error:
uninitialized constant Foo::Bar (NameError)
Here's another fun example:
module SomeName
class Client
end
end
module Integrations::SomeName::Importer
def perform
...
client = ::SomeName::Client.new(...)
...
end
end
That produces:
block in load_missing_constant': uninitialized constant Integrations::SomeName::Importer::SomeName (NameError)
Ruby (2.3.4) just goes to the first occurrence of "SomeName" it can find, not to the top-level.
A way to get around it is to either use better nesting of modules/classes(!!), or to use Kernel.const_get('SomeName')

Resources