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.
Related
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
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
I am still trying to clearly understand Module/Class/Instance variables ...
My code currently looks something like this ...
module Foo
##var1 ={}
##var2 =[]
##var3 = nil
def m1(value)
##var2 << value
end
def m2(value)
##var1[##var3]=value
end
end
class Bar
include Foo
p ##var1
end
class Bar2
include Foo
p #var1
end
I am trying to create a module that contains a class-wide configuration for how each class will behave. The configuration is stored in ##var1 and ##var2. Using this code the variables are shared across ALL classes that include the module. This is not the desire result, I want each class to have it's own behavior configuration.
I have also tried creating a single class that includes the module and also creates the variables but then the variables are not accessible by the module.
module Foo
def m1(value)
##var2 << value
end
def m2(value)
##var1[##var3]=value
end
end
class T
##var1 ={}
##var2 =[]
##var3 = nil
include foo
end
class Bar < T
p ##var1
end
class Bar2 < T
p #var1
end
I have also read that having modules with class variables is not good coding style but I cannot think of a way to achieve my functionality with this ...
Thanks in advance for any help
Firstly - class variables are evil and should be avoided (because they are also inherited by all subclasses and usually causes more harm than good.
You want to create a class instance variable (not class variable) on a class or module which is including given module. It is easy to do with included method:
module Foo
#default_settings = {}
module ClassMethods
def foo_settings
#foo_settings
end
end
def self.included(target)
target.instance_variable_set('#foo_settings', #default_settings.dup)
target.extend ClassMethods
end
end
I want to note that this is COMPLETELY a made up question. I am aware that there are other ways to accomplish this.
I want to declare a module like so
module Foo
# some logic here to
# get instance method 'foo' on
# a later defined class
end
then later I want to declare a class like:
class Foo::Bar
end
Then WITHOUT using include or extend be able to do this:
Foo::Bar.new.foo
and have it call the foo method I defined in module Foo
module Foo
class Bar
def foo
puts "erik is a dummy"
end
end
end
Foo::Bar.new.foo
=> erik is a dummy
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')