Load constant from string, as resolved in current scope - ruby

Say I have the following structure:
module Library
class DSL
def met(str)
# load `str` here; for this case, `MyApplication::MyClass`
end
end
class Superclass
extend DSL
end
end
module MyApplication
class MySubclass < Library::Superclass
met 'MyClass'
end
class MyClass
end
end
Inside Library::DSL#met I need to load constant str, which is provided as string. Obviously, it does not work if I simply do Object.const_get(str).
I need somehow to fully resolve str in the context of the current scope (MyApplication::MySubclass); str would therefore be resolved as MyApplication::MyClass. I need to fully resolve this because later I need to use this constant outside this namespace.

When resolving a constant, Ruby first checks the current nesting of modules and then the ancestors of the innermost module.
This can be replicated by traversing Module.nesting and Module.nesting.first.ancestors (in that order). If a module defines the constant, we can get its value via const_get:
class A
class B
def met
str = 'C'
mods = Module.nesting
mods.concat(mods.first.ancestors)
mod = mods.find { |c| c.const_defined?(str, false) }
mod.const_get(str)
end
end
class C
end
end
A::B.new.met #=> A::C

Related

In Ruby, in a method defined in class << self, why can't a constant defined on the superclass be access without self?

I'm trying to understand Ruby singletons and class inheritance better. I read everywhere that
def self.method_name; end`
is equivalent to
class << self
def method_name; end
end
But if that were true, then I would expect print_constant_fails to work, but it doesn't. What is going on here?
class SuperExample
A_CONSTANT = "super example constant"
end
class SubExample < SuperExample
def self.print_constant_works_1
puts A_CONSTANT
end
class << self
def print_constant_works_2
puts self::A_CONSTANT
end
def print_constant_fails
puts A_CONSTANT
end
end
end
pry(main)> SubExample.print_constant_works_1
super example constant
pry(main)> SubExample.print_constant_works_2
super example constant
pry(main)> SubExample.print_constant_fails
NameError: uninitialized constant #<Class:SubExample>::A_CONSTANT
from (pry):13:in `print_constant_fails'
You have encountered a common Ruby gotcha - constant lookup.
The most important concept in constant lookup is Module.nesting (unlike in method lookup, where the primary starting point is self). This method gives you the current module nesting which is directly used by the Ruby interpreter when resolving the constant token. The only way to modify the nesting is to use keywords class and module and it only includes modules and classes for which you used that keyword:
class A
Module.nesting #=> [A]
class B
Module.nesting #=> [A::B, A]
end
end
class A::B
Module.nesting #=> [A::B] sic! no A
end
In meta programming, a module or class can be defined dynamically using Class.new or Module.new - this does not affect nesting and is an extremely common cause of bugs (ah, also worth mentioning - constants are defined on the first module of Module.nesting):
module A
B = Class.new do
VALUE = 1
end
C = Class.new do
VALUE = 2
end
end
A::B::VALUE #=> uninitialized constant A::B::VALUE
A::VALUE #=> 2
The above code will generate two warnings: one for double initialization of constant A::VALUE and a second for reassigning the constant.
If it looks like "I'd never do that" - this also applies to all the constants defined within RSpec.describe (which internally calls Class.new), so if you define a constant within your rspec tests, they are most certainly global (unless you explicitly stated the module it is to be defined in with self::)
Now let's get back to your code:
class SubExample < SuperExample
puts Module.nesting.inspect #=> [SubExample]
class << self
puts Module.nesting.inspect #=> [#<Class:SubExample>, SubExample]
end
end
When resolving the constant, the interpreter first iterates over all the modules in Module.nesting and searches this constant within that module. So if nesting is [A::B, A] and we're looking for the constant with token C, the interpreter will look for A::B::C first and then A::C.
However, in your example, that will fail in both cases :). Then the interpreter starts searching ancestors of the first (and only first) module in Module.nesting. SubrExample.singleton_class.ancestors gives you:
[
#<Class:SubExample>,
#<Class:SuperExample>,
#<Class:Object>,
#<Class:BasicObject>,
Class,
Module,
Object,
Kernel,
BasicObject
]
As you can see - there is no SuperExample module, only its singleton class - which is why constant lookup within class << self fails (print_constant_fails).
The ancestors of Subclass are:
[
SubExample,
SuperExample,
Object,
Kernel,
BasicObject
]
We have SuperExample there, so the interpreter will manage to find SuperExample::A_CONSTANT within this nesting.
We're left with print_constant_works_2. This is an instance method on a singleton class, so self within this method is just SubExample. So, we're looking for SubExample::A_CONSTANT - constant lookup firstly searches on SubExample and, when that fails, on all its ancestors, including SuperExample.
It has to do with scope. When you are inside class << self, the scope is different than when you are inside class Something. Thus, inside class << self there is actually no constant called A_CONSTANT.
In Ruby, every Ruby's constant has its own path, start from the main (root) with the sign :: (default, we don't need to declare this sign). And class should not be considered a keyword (kind of static), but a method (kind of dynamic) take responsibility for creating a class object and a class name constant which point to that class object, and all Constants are defined inside a class without a path (P::Q::...) will automatically be considered belongs to the created class with path :: ClassName::A_CONSTANT.
GLOBAL = 1
class SuperExample
A_CONSTANT = "super constant" # <-- ::SuperExample::A_CONSTANT
end
puts ::GLOBAL # 1
puts ::SuperExample # SuperExample
puts ::SuperExample::A_CONSTANT # "super constant"
It looks like constants paths in children classes has same level with parent
class SubExample < SuperExample
end
puts ::SubExample::A_CONSTANT # "super constant"
As I noticed, all constants (without ::) inside the class block will be set path under the classpath, so when you get them, either you get with the explicitly constant path or under the class that constants belong to:
class SubExample < SuperExample
def self.print_constant_works_1
puts A_CONSTANT # ::SubExample::A_CONSTANT
end
def another
puts A_CONSTANT # ::SubExample::A_CONSTANT
end
def yet_another
puts SubExample::A_CONSTANT # ::SubExample::A_CONSTANT
end
end
Now check class << self
class SubExample < SuperExample
class << self
puts self # <Class:SubExample>
def print_constant_works_2
puts self::A_CONSTANT # declare explicitly constant path
end
def print_constant_fails
puts A_CONSTANT # not declare explicitly <-- Class:SubExample::A_CONSTANT
end
end
end
As you can see, the class inside class << self is different, so the path of constant A_CONSTANT inside method print_constant_fails is pointing to Class:SubExample which does not define any constant A_CONSTANT, so an error uninitialized constant #<Class:SubExample>::A_CONSTANT be raised.
Meanwhile print_constant_works_2 will work since we declare explicitly constant path, and self in this case is actually SubExample(call SubExample.print_constant_works_2).
Now let try with an explicit path ::A_CONSTANT inside print_constant_fails
def print_constant_fails
puts ::A_CONSTANT
end
The error be raised is uninitialized constant A_CONSTANT, ::A_CONSTANT is considered a global constant (main).

In Ruby, is it possible to have both Parent and child class in different Module

Is it possible to inherit a class which is in a separate module like Python allows? is there any other alternative that could be done without changing the structure drastically
File 1->
Module X
Class Y
end
end
File 2->
require relative 'path/to/File 1'
Module A
Class B
end
end
I want to inherit Y into B
It's possible like this,Parent class Hello is in module One and child class Hi is in module Two.
module X
class Y
def call_me
puts 'Hello Class'
end
end
end
module A
class B < X::Y
def call_me
puts 'Hi Class'
end
end
end
A::B.new.call_me
output
Hi Class
Is it possible to inherit a class which is in a separate module like Python allows?
Classes aren't "in modules", so the "module" part of your question is actually irrelevant. The rest of your question essentially boils down to "Is it possible to inherit a class", which is possible.
Constants can be namespaces within modules, but that does not create any relationship whatsoever between the class and the module. In your example, the class referenced by the constant Y is not in the module referenced by the constant X and the class referenced by the constant B is not in the module referenced by the constant A.
The constant Y is namespaced inside the module referenced by the constant X, but that only creates a relationship between the constant and the module, not between the class referenced by the constant and the module.
You can prove the fact that there is no relationship easily by simply assigning the class to a different variable:
foo = X::Y
class A::B < foo; end
Note: please don't let the default value of Class#name fool you. The default value of Class#name is indeed the full path of the first constant that the class is assigned to, even if you remove that constant again later:
module M
class C; end
end
module A
B = M::C
end
M::C = nil
M.send(:remove_const, :C)
M = nil
self.class.send(:remove_const, :M)
foo = A::B
A::B = nil
A.send(:remove_const, :B)
A = nil
self.class.send(:remove_const, :A)
# *None* of the constants exist anymore.
foo.name
#=> 'M::C'
# The `Class#name` property still stays the path of the very first constant

Ruby using class from "namespace"

I am using Ruby to create some simple project, and I am following RubyGems project structure. In my codebase I have two classes in different "namespaces":
lib
u
x
class_a.rb
m
p
class_b.rb
I am using nested modules for this classes, so ClassA is in module X which is in module U.
While requiring ClassA inside ClassB I can use it by referecing it with U::X::ClassA. I wonder if there is any pattern that will let me just typing ClassA, without full namespace.
You can do something like
module M::P
ClassA = U::X::ClassA
end
defining ClassA as a constant inside P. It's not a good practise, but you can do it..
IMO, just use U::X::ClassA.
Two comments in advance:
I agree to Fede, IMO, just use U::X::ClassA.
the namespace in ruby is independent from the file structure. So your example may explain what you want, but it does not give a code example.
That said, you may build a minimal example like this:
module U
module X
class Class_a
end
end
end
module M
module P
class Class_b
include U::X
def initialize
a = U::X::Class_a.new ##you want replace this with:
#a= Class_a.new
end
end
end
end
M::P::Class_b.new
Fedes solution with Class_a = U::X::Class_a would look like:
module U
module X
class Class_a
end
end
end
module M
module P
Class_a = U::X::Class_a ##<- define here the local version
class Class_b
def initialize
a= Class_a.new
end
end
end
end
M::P::Class_b.new
Another possibility is to include the module that contains the class:
module U
module X
class Class_a
end
end
end
module M
module P
class Class_b
include U::X #<- Include the module
def initialize
a= Class_a.new
end
end
end
end
M::P::Class_b.new
Attention: This solution will include all classes and constants of the module U::X. This may be a solution you need, but it may also be wrong for your purpose.

How can a ruby module enforce conditions on classes in which it is included?

How can I write a ruby module that imposes some conditions on classes in which it is included, which must be met the end of currently opened class definition?
To be concrete, suppose the condition is "a class variable ##foo should be defined to be >0 "
I'd like to write a module that looks something like this:
module NeedPositiveFoo
module ClassMethods
def validate_positive_foo
raise unless defined?(##foo) && ##foo > 0
end
end
def included(other)
other.extend(ClassMethods)
end
end
Then this class definition would be valid:
class ValidClass
include NeedPositiveFoo
##foo = 3
end
But these class definitions would raise after their closing end's:
class InvalidClass1
include NeedPositiveFoo
# ##foo is not defined
end
class InvalidClass2
include NeedPositiveFoo
##foo = -2
end
While the answer by Uri Agassi perfectly works when you are allowed to put includes in the very end of class definition, the code below will work despite where include was placed.
def included(other)
TracePoint.new(:end) do |tp|
if tp.self == other
tp.disable
raise unless defined?(other.class_variable_get(:##foo)) # checks
end
end.enable
other.extend(ClassMethods)
end
TracePoint documentation.
You can't hook the end of a class definition, because there isn't one - a class can be declared in different files, at different times, and in different libraries even.
What you can do, is check the condition when the module is included, and declare the inclusion at the end of your definition:
module NeedPositiveFoo
def included(other)
raise unless defined?(##foo) && ##foo > 0
end
end
class ValidClass
##foo = 3
include NeedPositiveFoo
end
class InvalidClass1
# ##foo is not defined
include NeedPositiveFoo
end
class InvalidClass2
##foo = -2
include NeedPositiveFoo
end
class InvalidClass3
include NeedPositiveFoo
##foo = 4 # declared after inclusion - not a valid state...
end
When asking questions like this, it is often useful to see how the Ruby core library does it. There are two well-known mixins in the Ruby core library which place certain conditions on the classes they are being mixed into:
Enumerable requires that the class have an each method that can be called with zero arguments and takes a block, to which it yields all the elements of the collection successively.
Comparable requires that the class have a <=> method that can be called with a single argument and responds with either -1, 0, or 1, depending on whether the argument is considered to be greater than, equal, or less than the receiver.
In both of these cases, the requirements are simply stated in the documentation, not in code. It is up to the class author to make sure they are met.
In fact, in the Enumerable case, the requirements are not really stated at all, it is just assumed that any competent Ruby programmer knows them.
I would follow this style set out by the authors of the core library, since it is what Rubyists are used to.

Refer to a class without explicitly mentioning its namespace

I have a class within several modules: This::Is::A::Long::ClassName. Is there any way, within one script or method, to make ClassName available without having to reference the namespace? Instead of writing:
This::Is::A::Long::ClassName.do_something
This::Is::A::Long::ClassName.do_something_else
This::Is::A::Long::ClassName.do_something_different
is anything as below possible?
include This::Is::A::Long
ClassName.do_something
ClassName.do_something_else
ClassName.do_something_different
If you are using modules for namespacing, the code you posted should work, see this example:
module Long
module Name
class ClassName
end
end
end
ClassName
# => ... uninitialized constant ClassName (NameError)
include Long::Name
ClassName
# => Long::Name::ClassName
Ruby has no equivalent to C++ using namespace, and you can not reference a class without being in the right namespace, but you can always make it a variable since a class is also an object
long_class = This::Is::A::Long::ClassName
long_class.do_something
long_class.do_something_else
# and so on
EDIT
An include does not put you in the right namespace, it includes the methods & classes in the module you are including (that is, it puts the module in the classes ancestors) and is therefore most certainly not suitable for your needs: Consider the following:
module This
module Is
module A
def foo
puts 'A#foo'
end
def bar
puts 'A#bar'
end
class ClassName
end
end
end
end
Now, you may not want to write This::Is::A::ClassName in another class, let's say:
class C
def foo
puts 'C#foo'
end
end
class B < C
include This::Is::A
end
Now, B.new.foo still puts out C#foo, right? Wrong. Since you included the module, the method has been overwritten.

Resources