I am trying to create a module to define some methods and constants for some classes.
This means the same method or constant can be defined in both the module and the class that includes the module.
In that scenario, I want to have control (preferably in the module definition, or maybe from the caller, without changing the class code) over which one takes precedence.
Often, I want the method or constant defined in the module to be used.
However, it looks like those defined in the class always get used unless I delete them from the class.
module A
def self.included(clazz)
const_set(:CONST, clazz.name == "B" ? "constant from module A for B" : "constant from module A for C")
define_method(:a) do
"from module A"
end
end
end
class B
include A
CONST = "constant from class B"
def a
"from class B"
end
end
class C < B
include A
CONST = "constant from class C"
def a
"from class C"
end
end
b = B.new
c = C.new
p b.a, c.a
p B::CONST, C::CONST
Running this on this REPL prints
main.rb:3: warning: already initialized constant A::CONST
main.rb:3: warning: previous definition of CONST was here
"from class B"
"from class C"
"constant from class B"
"constant from class C"
Is there a way I can make this output
"from class A"
"from class A"
"constant from module A for B"
"constant from module A for C"
without deleting constant CONST or method a from class B or C?
First, Ruby interpret code line by line from top to bottom, so in your case, Ruby read module A before the line define CONST =, as a result, the CONST defined in class B or C will always the output.
-> move include A below CONST=
Second, inside def self.included(clazz) the self is the module A not the class B or C, as a result, the const_set method be called by the module A itself so there's no B::CONST or C::CONST is defined here.
-> use clazz.const_set inside def self.included(clazz)
try this
module A
def self.included(base)
base.send :remove_const, :CONST if base.const_defined?(:CONST)
base.const_set(:CONST, base.name == "B" ? "constant from module A for B" : "constant from module A for C")
end
end
class B
CONST = "constant from class B"
include A
end
Related
I have a module (module A) that reads a JSON file based on the names of the classes that include it (B.json for class B, C.json for class C, etc.) and defines some methods and constants for them.
The only place I found that I have access to the class name was self.included method, but I couldn't access it from outside it.
I tried different things, including setting instance variables from self.included or the constructors of the classes, but had no luck.
Here is my code example:
https://replit.com/#EricNa1/CornyClutteredKeyboardmacro#main.rb
module A
puts "class name doesn't print here:", self.name
# I want to read a JSON file here based on the class name (e.g. B.json for B, C.json for C, etc.)
def self.included(clazz)
puts "but it prints here", clazz.name
end
end
class B
include A
end
class C
include A
end
B.new()
C.new()
prints
class name doesn't print here:
A
but it prints here
B
but it prints here
C
(Also I'm not sure why 'class name doesn't print here: A' gets printed only once even though it's supposed to be called once from B and once from C)
Your code works fine, it prints:
class name doesn't print here:
A
not because you have included module, it prints because of module created.
You can comment your class and see that message still prints.
Next, method:
def self.included(clazz)
puts "but it prints here", clazz.name
end
is called when module is included in class or somewhere, class doesn't call it at all, because class even does not have access to that method, because the method is with 'self' and module is included, not extended to class.
So you can fetch all class names which include that module with something like that:
module Super
#klass_names = []
def self.included(klass)
# puts "Class which included me " + klass.name
#klass_names << klass.name
end
def self.show_class_names
p #klass_names
end
end
class M
include Super
end
class B
include Super
end
Super.show_class_names
This code is not perfect at all, but at least it gives you an idea.
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).
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
I have classes A and B, each of which needs some constant from the other. When I do this:
class A
puts B::CONSTANT
CONSTANT = "A constant"
end
class B
puts A::CONSTANT
CONSTANT = "B constant"
end
then I get this error:
NameError: uninitialized constant B::CONSTANT
A circular dependency exists, as each class needs the other class to be loaded first. What is the solution for such case?
Edit
In my original case there was no circular dependency, but I had a class A that uses a constant from another class B, however A was loaded before B so an exception was thrown, then I forced the load of B before A, but then I thought: what will I do if I have circular dependency?
I don't understand why you have to have only one class body for each class, and have the contents in that order. Simply do:
class A
CONSTANT = "A constant"
end
class B
CONSTANT = "B constant"
end
class A
puts B::CONSTANT
end
class B
puts A::CONSTANT
end
What's the difference between:
class A
class B
end
end
and
class A
end
class A::B
end
Update: These 2 approaches are not exactly the same.
In the second approach, B doesn't have access to constants defined in A.
Also, as Matheus Moreira correctly stated, in the second approach, A must be defined before A::B can be defined.
What other differences are there?
In Ruby, modules and classes are instances of the Module and Class classes, respectively. They derive their names from the constant they are assigned to. When you write:
class A::B
# ...
end
You are effectively writing:
A::B ||= Class.new do
# ...
end
Which is valid constant assignment syntax, and assumes that the A constant has been properly initialized and that it refers to a Module or a Class.
For example, consider how classes are usually defined:
class A
# ...
end
What is effectively happening is this:
Object::A ||= Class.new do
# ...
end
Now, when you write:
class A
class B
# ...
end
end
What actually happens looks like this:
(Object::A ||= Class.new).class_eval do
(A::B ||= Class.new).class_eval do
# ...
end
end
Here's what is happening, in order:
A new Class instance is asssigned to the A constant of Object, unless it was already initialized.
A new Class instance is asssigned to the B constant of A, unless it was already initialized.
This ensures the existence of all outer classes before attempting to define any inner classes.
There is also a change in scope, which allows you to directly access A's constants. Compare:
class A
MESSAGE = "I'm here!"
end
# Scope of Object
class A::B
# Scope of B
puts MESSAGE # NameError: uninitialized constant A::B::MESSAGE
end
# Scope of Object
class A
# Scope of A
class B
# Scope of B
puts MESSAGE # I'm here!
end
end
According to this blog post, the Ruby core team calls the "current class" the cref. Unfortunately, the author does not elaborate, but as he notes, it is separate from the context of self.
As explained here, the cref is a linked list that represents the nesting of modules at some point in time.
The current cref is used for constant and class variable lookup and
for def, undef and alias.
As the others have stated, they are different ways of expressing the same thing.
There is, however, a subtle difference. When you write class A::B, you assume that the A class has already been defined. If it has not, you will get a NameError and B will not be defined at all.
Writing properly nested modules:
class A
class B
end
end
Ensures the A class exists before attempting to define B.
Two different ways to say the same thing. That thing is that class B is an inner or nested class and can only be accessed through the the A interface.
> class A
.. def say
.... "In A"
....end
..
.. class B
.... def say
...... "In B"
......end
....end
..end
=> nil
> A.new.say
=> "In A"
> B.new.say
=> #<NameError: uninitialized constant B>
> A::B.new.say
=> "In B"
versus
> class A
.. def say
.... "In A"
....end
..end
=> nil
> class A::B
.. def say
.... "In B"
....end
..end
=> nil
> A.new.say
=> "In A"
> B.new.say
=> #<NameError: uninitialized constant B>
> A::B.new.say
=> "In B"
>
They are the same. They are different ways of writing the same thing. The first one is the naive way of writing it, but often, it gets hard to keep track of the nesting once the class/module gets large. Using the second way, you can avoid nesting in the appearance.