Allow unqualified access to Ruby constants in a different module - ruby

According to this answer, one can get a constant into the global namespace with an include at the top level, at least in IRB. Naively, I thought I could do the same trick inside a module:
module Foo
class Bar
def frob(val)
"frobbed #{val}"
end
end
end
module Baz
include Foo
class Qux
def initialize(val)
#val = val
#bar = Bar.new
end
def twiddle
#bar.frob(#val)
end
end
end
However, attempting to use this produces:
NameError: uninitialized constant Baz::Qux::Bar
I then tried overriding Baz::const_missing to forward to Foo, but it doesn't get called. Baz::Qux::const_missing does, but I suppose by that time the interpreter has already given up on looking in Baz.
How can I import Foo constants into Baz such that classes under Baz don't have to qualify them?
(Note that I don't just want to declare Qux etc. in Foo instead of Baz, for reasons of consistency between directory structure and module names.)
ETA: If I create another class Baz::Corge, Baz::Qux can refer to it without qualification. So clearly there is some sense of "peer scope". I'm aware there are several ways to make it possible for Qux to access Bar, but I'm looking for a way for all classes in Baz to access Bar (and all other classes in Foo) without qualification.

Ok.. The problems is that the constant are (for some reason) not defined inside the Quz class, but in the upper scope, the module Baz scope.
If only we could somehow delegate (current word?) the constants calls in Quz to the upper (Baz) scope- We can:
Using Module#const_missing
I'll show it twice: once for your private case, and once for a more general approach.
1) solve for private case:
module Foo
class Bar
def frob(val)
"frobbed #{val}"
end
end
end
module Baz
include Foo
class Qux
def self.const_missing(c)
#const_get("#{self.to_s.split('::')[-2]}::#{c}")
const_get("Baz::#{c}")
end
def initialize(val)
#val = val
#bar = Bar.new
end
def twiddle
#bar.frob(#val)
end
end
end
puts Baz::Qux.new('My Value').twiddle
So what happens here? almost same thing- only that when the error is received- it's kinda rescued and arriving (or fall back to) the const_missing function to receive a new value- This apply for both Constants and Classes (apparently they are the same type).
But that mean we have to add the self.const_missing(c) method to every class inside module Baz- Or we can just iterate every classes in module Baz and add it (There are probably better ways to do it, but it works)
2) A more automated approach:
module Foo
class Bar
def frob(val)
"frobbed #{val}"
end
end
end
module Baz
class Qux
def initialize(val)
#val = val
#bar = Bar.new
end
def twiddle
#bar.frob(#val)
end
end
def self.add_const_missing_to_classes
module_name = self.to_s
#note 1
classes_arr = constants.select{|x| const_get(x).instance_of? Class} #since constants get also constants we only take classes
#iterate classes inside module Baz and for each adding the const_missing method
classes_arr.each do |klass| #for example klass is Qux
const_get(klass).define_singleton_method(:const_missing) do |c|
const_get("#{module_name}::#{c}")
end
end
end
add_const_missing_to_classes
#we moved the include Foo to the end so that (see note1) constants doesn't return Foo's classes as well
include Foo
end
puts Baz::Qux.new('My Value').twiddle
Here at the end of module Baz, after all classes were defined. we iterate them (inside the add_const_missing_to_classes method). to select them we use the Module#constants method which returns an array of a module constants- meaning both CONSTANTS and CLASSES, so we use select method to only work on classes.
Then we iterate the found classes and add the const_missing class method to the classes.
Notice we moved the include Foo method to the end- because we wanted the constants method not to include constants from module Foo.
Surly there are better ways to do it. But I believe the OP's question:
How can I import Foo constants into Baz such that classes under Baz don't have to qualify them?
Is answered

Here's what I eventually came up with. In the same file that initially declares Baz:
module Foo
def self.included(base)
constants.each { |c| base.const_set(c, const_get("#{self}::#{c}")) }
end
end
module Baz
include Foo
#... etc.
end
When Baz includes Foo, it will set a corresponding constant Baz::Whatever for every Foo::Whatever, with Foo::Whatever as the value.
If you're worried Foo may already define self.included, you can use alias_method to adjust for that:
module Foo
alias_method :old_included, :included if self.method_defined? :included
def self.included(base)
old_included(base) if method_defined? :old_included
constants.each { |c| base.const_set(c, const_get("#{self}::#{c}")) }
end
end
This approach has two limitations --
All the constants (including classes) in Foo that we care about must be defined at the time include Foo is evaluated -- extensions added to Foo later will not be captured.
The file that defines Foo.included here must be required before any file in Baz that uses any of those constants -- simple enough if clients are just using require 'baz' to pull in a baz.rb that in turn uses Dir.glob or similar to load all the other Baz files, but it's important not to require those files directly.
Roko's answer gets around problem (1) above using const_missing, but it still has an analogous problem to (2), in that one has to ensure add_const_missing_to_classes is called after all classes in Baz are defined. It's a shame there's no const_added hook.
I suspect the const_missing approach also suffers performance-wise by depending on const_missing and const_get for every constant reference. This might be mitigated by a hybrid that caches the results, i.e. by calling const_set in const_missing, but I haven't explored that since trying to figure out scoping inside define_singleton_method always gives me a headache.

As you can see from the error given: NameError: uninitialized constant Baz::Qux::Bar
It tries to find the Bar class inside Qux class scope- But can't find it there- why is that?
Because it's not in this scope- It's in the Baz modlue scope, where you used include Foo
So you have two options:
1) address the correct scope when calling Bar class,
so change this:
#bar = Bar.new
into this:
#bar = Baz::Bar.new
Like that:
module Baz
include Foo
class Qux
def initialize(val)
#val = val
#bar = Baz::Bar.new
end
def twiddle
#bar.frob(#val)
end
end
end
Or,
2) insert the include Foo into the class Qux itself:
module Baz
class Qux
include Foo
def initialize(val)
#val = val
#bar = Bar.new
end
def twiddle
#bar.frob(#val)
end
end
end
--EDIT--
As stated by joanbm this doesn't explain this behavior. You might want to take a look at Scope of Constants in Ruby Modules. Though that post is about constants (not classes) the principles are the same.

Since Foo is included in Baz, that's where Bar is found -- it isn't found in Qux. So you can change
#bar = Bar.new
to
#bar = Baz::Bar.new
or move include Foo inside class Qux

Related

How different ways to define a class influence the way include works?

I have a simple module that defines a constant and makes it private:
module Foo
Bar = "Bar"
private_constant :Bar
end
I can include it in a class like this, and it works as expected:
class User
include Foo
def self.test
Bar
end
end
puts User.test
# => Bar
begin
User::Bar
rescue => exception
puts "#{exception} as expected"
# => private constant Foo::Bar referenced as expected
end
(let's call it "typical class" definition)
Then I tried the Class.new approach, but this failed miserably:
X = Class.new do
include Foo
def self.test
Bar # Line 28 pointed in the stack trace
end
end
begin
X::Bar
rescue => exception
puts "#{exception}"
# => private constant Foo::Bar
end
puts X.test
# test.rb:28:in `test': uninitialized constant Bar (NameError)
# from test.rb:28:in `<main>'
Why? I always though class Something and Something = Class.new are equivalent. What's the actual difference?
Then I had a strike of inspiration, and recalled there's alternative way to define class methods, which actually worked:
X = Class.new do
class << self
include Foo
def test
Bar
end
end
end
begin
X::Bar
rescue => exception
puts "#{exception}"
# => uninitialized constant X::Bar
end
puts X.test
# Bar
Again - why this one work, and why the exception is now different: private constant Foo::Bar vs uninitialized constant X::Bar?
It seems like those 3 ways of initializing classes differ in a nuanced way.
does exactly what I want: Bar is accessible internally, and accessing it gives exception about referencing private constant.
second gives "ok" exception, but has no access to Bar itself
third has access, but now gives slightly different exception
What is exactly going on in here?
This is one of the biggest gotchas in Ruby: constant definition scope is partially syntactic, that is, it depends on how the code around it is structured.
module Foo
Bar = "Bar"
end
Bar is inside a module definition, so it is defined in that module.
class << self
include Foo
end
Bar gets included inside a class definition, so it is defined in that class.
Class.new do
include Foo
end
There is no enclosing class or module (this is a normal method call with a block), so the constant is defined at top level.
As for your third error, I believe that is because the constant got defined in the singleton class (that's what class << self is) versus the class itself. They are two separate class objects.

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")

How can I extend a ruby class from a class defined in a module?

I have the following files:
file.rb
require_relative 'foo/bar'
baz = Foo::Stuff::Baz.new
# do stuff
foo/bar.rb
require_relative 'stuff/baz'
module Foo
class Bar
def initialize
# do stuff
end
end
end
foo/stuff/baz.rb
module Foo
module Stuff
class Baz < Bar
end
end
end
I get the following error:
`': uninitialized constant Foo::Stuff::Bar (NameError)
Is there something I'm doing wrong here? Is this even possible in Ruby? In case it matters, I'm only doing this because I need to inherit the initialize method specifically.
It works just fine when you put them in the same script :
module Foo
class Bar
def initialize
# do stuff
end
end
end
module Foo
module Stuff
class Baz < Bar
end
end
end
p Foo::Stuff::Baz.ancestors
#=> [Foo::Stuff::Baz, Foo::Bar, Object, Kernel, BasicObject]
So it must be a problem with the way or order in which you require your files.
Also, if you just need just one specific method from Foo::Bar in Foo::Stuff::Baz, you could put this method in a module, and include this module in both classes.
Your foo/stuff/baz.rb does not contain any require statement and you tell nothing about a main programm. So I think you just don't load the code.
Ruby has no automatic loading depending on folder path, you must explicitly load the source code. In your case you need a require_relative '../bar' in the file foo/stuff/baz.rb. Then the class Foo::Bar is known:
require_relative '../bar'
module Foo
module Stuff
class Baz < Bar
end
end
end
p Foo::Stuff::Baz.new
p Foo::Stuff::Baz.ancestors
The result:
#<Foo::Stuff::Baz:0x00000002ff3c30>
[Foo::Stuff::Baz, Foo::Bar, Object, Kernel, BasicObject]
The initialize-method of Foo::Bar is executed.
A more realistic architecture would be the usage of a main file where you load all code files, e.g.:
foo.rb
foo/bar.rb
foo/stuff/baz.rb
and foo.rb would contain:
require_relative 'foo/bar'
require_relative 'foo/stuff/baz'
Foo::Bar is defined. You can also access ::Foo::Bar ("root" module) when there are issues finding the right namespace.
It does not work because in baz.rb namespace there is no any reference to the Bar class; should simply enter:
class Bar; end
So the baz.rb structure becomes simply have: (foo/stuff/baz.rb)
module Foo
class Bar; end
module Stuff
class Baz < Bar
end
end
end

Meaning of self keyword in ruby [duplicate]

From what I understand about self, it refers to the current instance of the class.
Isn't this the default behaviour at all times anyways? For example, isn't
self.var_one = method(args)
equivalent to
var_one = method(args)
If so, what is the use of self?
There are several important uses, most of which are basically to disambiguate between instance methods, class methods, and variables.
First, this is the best way to define class methods:
class Foo
def self.bar
"class method bar"
end
def bar
"instance method bar"
end
end
Foo.bar #returns "class method bar"
foo = Foo.new
foo.bar #returns "instance method bar"
Also, within instance methods self refers to the instance, within class methods it refers to the class, and it can always be used to distinguish from local variables.
class Bar
def self.foo
"foo!"
end
def baz
"baz!"
end
def self.success
foo #looks for variable foo, doesn't find one, looks for class method foo, finds it, returns "foo!"
end
def self.fail
baz #looks for variable baz, doesn't find one, looks for class method baz, doesn't find one, raises exception
end
def instance_success
baz #looks for variable baz, doesn't find one, looks for instance method baz, finds it, returns "baz!"
end
def instance_fail
foo #looks for variable foo, doesn't find one, looks for instance method foo, doesn't find one, raises exception
end
def local_variable
baz = "is my favorite method"
baz #looks for variable baz, finds it, returns "is my favorite method"
end
def disambiguate
baz = " is my favorite method"
self.baz + baz #looks for instance method baz, finds it, looks for local variable baz, finds it, returns "baz! is my favorite method"
end
end
So, in the end, you can avoid using self in many cases, but it's often helpful to use it to make sure that you don't inadvertently create naming conflicts later on. Sometimes those can create bugs that are very hard to find. In the end it's often a matter of personal style.
As noted in the comments, one more really important thing:
In a class, if you have a method like this:
def bar=(string)
...
end
And in another method you call:
def other_method
bar = "abcd"
end
It isn't going to call your bar= method, it's going to create a local variable bar. So, in this case you use self to tell Ruby not to create a local variable:
def other_method
self.bar = "abcd"
end
The same thing applies if you want to take an argument with the name of a method:
def example
...
end
def other_thing(example)
self.example(example)
end
If you left off self Ruby would assume you meant the local variable with the same name.
So, in general, self in method names is used to distinguish between class and instance variables, and everywhere else you use it when Ruby needs help distinguishing between method calls and local variables or local variable assignment.
I hope that makes sense.
In most cases self.foo is indeed redundant because you can just write foo for the same effect, but in this case it is not and the self is required.
var_one = method(args) will create a local variable called var_one, it will not call any method or do anything else to self.
self.var_one = method(args) will call the method var_one= on self with the argument method(args).
Another case where the use of self is non-optional would be if you want to pass it as an argument to a method, i.e. some_method(self) - you can't do that without the self keyword.
One other use of self is to declare class methods (similar to static methods in Java).
class foo
def self.bar
#do class related stuff here
end
end
That being said, you could also have used def foo.bar instead for the method signature.
Here's an example:
def run miles
self.miles = miles
end
In this case self will help. In most cases self is redundant.

Resources