How to autoload a class in ruby - ruby

I have a module as follows
-path of class MyClass lib/a/b/myclass.rb
module A
module B
class MyClass
puts 'inside myclass'
end
end
end
Now i want to autoload the above class from a file in root directory.
File name : dostuff
def main
autoload A::B:MyClass,'a/b/myclass.rb' #path is correct , getting error here
c = A::B:MyClass.new
end
main
Am getting error: uninitialized constant A::B::MyClass (NameError)
If i use require as follows and delete autoload code everything works fins.
require 'a/b/myclass'

You're asking too much of the auto-loader. It can really only deal with one level at a time. That means you need this by expressing each module or class in a separate file:
# a.rb
module A
autoload(:B, 'a/b')
end
# a/b.rb
module A::B
autoload(:MyClass, 'a/b/my_class')
end
# a/b/my_class.rb
class A::B::MyClass
end
Then you can auto-load A:
autoload(:A, 'a')
A::B::MyClass.new
It's also highly unconventional to have a main function in Ruby. Normally you just put code at the top level in a context that is already called main.

Related

Ruby metaprogramming: define_method block not maintaining scope

I'm working on dynamically patching a bunch of classes and methods(most of the time these methods are not simple "puts" like a lot of examples I've been able to find on the internet)
Say for instance I have the following code:
foo.rb
module Base
class Foo
def info
puts 'Foo#info called'
end
end
end
& I also have the following class:
test.rb
module Base
class Test
def print
puts "Test#print called"
Foo.new.info
end
end
end
Then in main.rb I have the following where I want to add a method that uses a class within the same module(Foo in this case)
require_relative './foo'
require_relative './test'
new_method_content = "puts 'hi'
Foo.new.info"
Base::Test.instance_eval do
def asdf
puts "Test#asdf called"
Foo.new.info
end
end
Which, when executed will net the following:
Uncaught exception: uninitialized constant Foo
Which sort of makes sense to me because the main.rb file doesn't know that I want Base::Foo, however, I need a way to maintain lookup scope because Base::Test should be able to find the class Foo that I want.
Base::Test.instance_eval do
def asdf
puts "Test#asdf called"
Foo.new.info
end
end
I've done a fair bit of googling and SO'ing but haven't found anything about how to maintain constant lookup scope while class_eval/instance_eval/module_eval/define_method(I've tried a lot of Ruby's dark magic methods all of which have ended in varying degrees of failure lol)
https://cirw.in/blog/constant-lookup
Confusingly however, if you pass a String to these methods, then the String is evaluated with Module.nesting containing just the class itself (for class_eval) or just the singleton class of the object (for instance_eval).
& also this:
https://bugs.ruby-lang.org/issues/6838
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 my question is:
How can I redefine a method BUT maintain constant/class scope?
I've been trying a bunch of other things(in the context of main.rb):
Base::Test.class_eval('def asdf; puts "Test#asdf called"; Foo.new.info; end')
Base::Test.new.asdf
=>
Test#asdf called
Uncaught exception: uninitialized constant Base::Test::Foo
Did you mean? Base::Foo
(which is a diff problem in that it's trying to look it up from the evaluated module nesting? I'm not sure why it doesn't try all module paths available from Base::Test though, I would think it would try Base::Test::Foo which doesn't exist, so then it would go up the module tree looking for the class(Base::Foo) which would exist)
When you reference the class Base::Test like this, ruby does not take the Base:: as the module context to look up constants. That is the normal behaviour and would also not work, if you would define the moethod directly.
But you could do it in this way:
module Base
Test.instance_eval do
def asdf
puts "Test#asdf called"
Foo.new.info
end
end
end

How to require file as part of a module in Ruby?

I have a file SomethingClass.rb which looks as follows:
class SomethingClass
def initialize
puts "Hello World"
end
end
I would like to require the file SomethingClass.rb, and make SomethingClass part of the module SomethingModule without changing the file.
Also, I would like to avoid making SomethingClass part of the namespace outside of that module at all. In other words, I want to require the file and the rest of my application should not change apart from the fact that SomethingModule will be defined.
This does not work (I assume because require is executed in Kernel scope):
module SomethingModule
require './SomethingClass.rb'
end
Is this possible in Ruby?
Without changing your class file, from what I've gathered, there are only kind of hacky ways to do this - see Load Ruby gem into a user-defined namespace and How to undefine class in Ruby?.
However I think if you allow yourself to modify the class file, it is a little easier. Probably the simplest thing to do would be to set the original class name to something that will surely have no name conflict, e.g.:
class PrivateSomethingClass
def initialize
puts "Hello World"
end
end
module SomethingModule
SomethingClass = PrivateSomethingClass
end
Now you have SomethingModule::SomethingClass defined but not SomethingClass on the global namespace.
Another way to do it would be to use a factory method and anonymous class:
class SomethingClassFactory
def self.build
Class.new do
def initialize
"hello world"
end
end
end
end
module SomethingModule
SomethingClass = SomethingClassFactory.build
end

Added method to Ruby class throws NoMethodError in MiniTest

So why is this happening? It has to be a namespace error, I just don't understand where it is. I add a method to Fixnum like so in a file file.rb
module M
class Fixnum
def foo
return true
end
end
end
then I'll make a test like so:
require 'minitest/autorun'
require './file.rb' #the path is correct
class SomeTest < MiniTest::Test
def test_foo
assert 3.foo
end
end
which will in turn throw a
NoMethodError: undefined method `foo' for 3:Fixnum
when I run the test, and I am left scratching my head - even if I include M to include the module (applying the namespace?) for the test it still throws the error. I can use custom classes just fine, it's only when I try to add a method to an existing "open class".
Yes, you have defined your own M::Fixnum class which actually has nothing to do with ::Fixnum in the global namespace. The following will solve an issue:
module M
class ::Fixnum
def foo
return true
end
end
end
5.foo
#⇒ true
Please note, in the code above module M has no sense, since the code nevertheless monkey-patches the global Fixnum. The code is here just to show how you would monkey-patch the global class from inside another module code.
Plus, Ruby2 introduced refinements, which are likely what you are intended to use.

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.

Can't include ruby modules correctly

I'm kinda new to Ruby, so I'm not even sure if what I'm doing is best practice. Right now I am trying to define a function import that resides in a module on something.rb:
require 'rexml/document'
module MyModule
def import(file)
Document.new(File.new(file))
end
end
I have another file somethingelse.rb that calls on file something.rb that will use function import
require 'something.rb'
class MyClass
include MyModule
def initialize(file)
#myFile = import(file)
end
end
The problem only arises when I try to import the module from another file. When I use the module in the same file, everything works according to what you'd expect. The errors I get are:
usr/lib/ruby/1.8/rexml/dtd/elementdecl.rb:8: warning: already initialized constant PATTERN_RE
XMLTest.rb:9: uninitialized constant MyModule (NameError)
What am I doing wrong?
You need to require the other file you're trying to load in your first file, Ruby won't do that for you automatically. So if your module is in a file named "something.rb":
require "something"
class MyClass
include MyModule
def initialize(file)
#myFile = import(file)
end
end
try changing your rexml require to require_once.
So:
require_once 'rexml/document'
you can you use require_relative to import the file that has your module
use include to add the module in the class to access the module
class MyClass
include somethingModuleName
end

Resources