Reputation: 13
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)
Upvotes: 0
Views: 375
Reputation: 2257
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
Upvotes: 1