Ruby: class in different files but using each other - ruby

In one of my projects, I need to put two class definitions in one file, and their parent class in another. After I required the parent file, it's still giving uninitialized constant Foo. Checkout the code below. The problem is: How to make it work(run from file2)?
#file1.rb
require_relative './file2.rb'
class Bar < Foo
get('/bar') { 'bar' }
end
class Car < Foo
end
#file2.rb
require_relative './file1.rb'
class Foo
get('/foo') { 'foo' }
end
class Dar < Foo
Bar.new
Car.new
end

Define your parent classes first and your derived ones second. This also makes your code a lot easier to navigate since it's in a predictable order, prerequisites first, specializations later.
Defining it last is impossible, Ruby absolutely needs to know what Foo is first. You can get away with stubbing it in, though:
class Foo
end
class Bar < Foo
end
class Foo
# Re-open class and add functionality
get('...)
end
I'd recommend steering away from this approach whenever possible. It usually ends up accumulating code in both places and nasty duplication errors can occur.

I'm not sure if it is about circular dependency in this problem, but I do figure out a way to avoid the bug. Use another file require both files, and execute from the new file.
#file1.rb
class Bar < Foo
get('/bar') { 'bar' }
end
class Car < Foo
end
#file2.rb
class Foo
get('/foo') { 'foo' }
end
class Dar < Foo
Bar.new
Car.new
end
#file_root.rb
require './file1'
require './file2'
FYI, you probably already figured out I'm using Sinatra from the 'get' method. So the file_root.rb file will actually be the config.ru.

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.

What does `class Foo < self` do in Ruby?

Looking through the sourcecode of the redis-store RubyGem, I stumbled upon this syntax I hadn't seen before:
class Foo < self
# ...
end
My Google-Fu apparently isn't powerful enough, because I've been able to find nothing that describes what this does.
What I'm guessing this does, is somehow reopening Foo, extending it with itself as superclass, thereby making it possible to override methods that can call the original definition as super. Am I close?
class Foo < Bar
end
Is how you tell Ruby that Foo inherits from Bar.
Within a class definition, self refers to the class itself:
# prints Foo
class Foo
puts self
end
So
class Foo
class Bar < self
end
end
Just says that Bar is nested under Foo and it inherits from it.
The main thing that you seem to be messing is just this: the superclass portion of a Ruby class definition is an arbitrary Ruby expression.
So, something like this is perfectly legal:
class Foo < if rand < 0.5 then Bar else Qux end end
Obviously, this doesn't make sense, but for example, in _why the lucky stiff's brilliant little web micro framework Camping, routes are defined like this:
class Edit < R '/post/(\d+)/edit'
and Migrations are defined like this:
class BlogInitialSchemaCreation < V 1.0
In ActiveRecord, migrations are defined like this:
class CreateProducts < ActiveRecord::Migration[5.0]
All of this simply uses the fact that whatever is to the right of the < in a class definition can by any arbitrary Ruby expression which is evaluated when the class definition is evaluated.

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 to raise error when including a module that already has same-name methods

Given:
module Foo
def bar
puts 'Foo Bar!'
end
end
class Person
def bar
puts 'Person Bar!'
end
end
class Person
# I want this line to raise an error because Foo and Person has same-named methods that would break dependencies if ignored
include Foo
end
Person.new.bar
# => Person Bar!
Question:
From the code above, I am looking for an alternative to include and extend that would do the same thing except that it will raise an error when included or extended methods are already defined.
Is there something like include! or extend!?
Usage:
I am writing a small gem that would be adding methods to the Object class:
class Object
include MyGemName
end
, and I want to make sure first that there are no same-name methods defined yet on the Object class.
Attempts:
class Person
duplicate_methods = self.instance_methods(false) & Foo.instance_methods(false)
raise "#{self} already has methods: #{duplicate_methods}" unless duplicate_methods.empty?
include Foo
end
above works, but because this is my first gem and that I feel that "naming" is a "usual" programming problem, that perhaps there is just some "simpler" way of doing this.
Note:
It is a requirement for me to override the Object class, because I want to skip passing self as an argument to the said methods.
I might be missing just simple thing here, but my search over the internet yields me no helpful solution. Perhaps then, I'm doing something wrong?
If you really want this behaviour, you could consider the following:
module Foo
def self.included(base)
overrides = instance_methods.select { |method| base.instance_method(method).owner != self }
raise "#{self.name} overrides #{overrides.join(', ')}" if overrides.any?
end
end
Running your code would get the following results:
in `included': Foo overrides bar (RuntimeError)

Allow unqualified access to Ruby constants in a different module

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

Resources