Added method to Ruby class throws NoMethodError in MiniTest - ruby

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.

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

NoMethodError: undefined method `meow?' for # - ruby mixin failing?

I'm playing with some of the very basics of ruby mixins, and for some reason can't access behavior from my module.
Running this on Ruby Fiddle:
module Cats
MEOW = "meow meow meow"
def Cats.meow?
return Cats::MEOW
end
end
class Example
include Cats
def sample
return "it's a sample"
end
end
e = Example.new
puts e.sample
puts e.meow?
This keeps returning NoMethodError: undefined method 'meow?' for #
My understanding of how mixins should work from tutorialspoint makes me feel like I should be able to validly call e.meow?, and get back the same result I would get from calling Cats.meow?.
Here's the code in RubyFiddle.
Incredibly basic, but any ideas where I'm falling down here?
As it turns out, I was being overly specific when defining Cats.meow?. If you want to use a module as a mixin you'll want to define your methods more generally, not with respect to their specific module namespace.
So instead of
def Cats.meow?
...
end
it should have been
def meow?
...
end
This lets you call e.meow?, since the method definition no longer limits it just to the Cats namespace.
Whoops.
As a general rule to using include and extend in Ruby:
If you want to use your module as a namespace
module Outer
module Inner
def self.my_method
"namespaced method!"
end
end
end
You use it like this Outer::Inner::my_method or Outer::Inner.my_method.
And if you want to use the module as a mixin:
# In some cases it makes sense to use names ending in -able, since it expreses
# what kind of messages you can send to an instance or class that mixes
# this module in.
# Like Devise's Recoverable module: https://github.com/plataformatec/devise/blob/f39c6fd92774cb66f96f546d8d5e8281542b4e78/lib/devise/models/recoverable.rb#L24
module Fooable
def foo
"#{self} has been foo'ed!"
end
end
Then you can include it (instances of Something obtain #foo):
class Something
include Fooable # Now Something.new can receive the #foo message.
end
Something.new.foo
=> "#<Something:0x0055c2dc104650> has been foo'ed!"
Or you can extend it (Something itself obtains #foo as a class message):
class Something
extend Fooable # Now Something can receive the #foo message.
end
Something.foo
=> "Something has been foo'ed!"

Why am I getting a NoMethodError when calling an instance method from global scope?

I have searched around for the answer to this and I can see a lot of similar problems but I still do not understand what I am doing wrong here. I have declared a Ruby class and attempted to new it and then call some instance methods on the instance, so why do I get the NoMethodError on my start method?
class MyClass
def initialize
self.class.reset
end
def self.reset
...
end
def self.start(port)
...
end
end
test = MyClass.new
test.start '8082' <- here <- undefined method `start' for #<MyClass:0x2f494b0> (NoMethodError)
As you can see I am a Ruby noob. Any help would be appreciated. I can change my class structure but I would really like to understand what I am doing wrong here.
here start is a class method.
By your current approach, you can use it in the following way
MyClass.start '8080'
But if you want to use it on instance of class then use the following code
class MyClass
def initialize
self.class.reset
end
def self.reset
...
end
def start(port)
...
end
end
test = MyClass.new
test.start '8080'
You are using start as a Class variable, the method names preceded with self-keyword make those methods as Class methods. So if you really want to not change your class then you should call it like this:
MyClass.start '8080'
Else you can remove the self from your reset and start methods and make them as Instance methods and use them as:
test = MyClass.new
test.start '8082'

strange output on calling base class method from extended module

What I want to achieve is something like below, i.e. calling a base class method from extended modules method:
class BaseClass
def behavior
puts 'base class behavior'
end
end
module ChildModule
def behavior
super.behavior
puts 'child module behavior'
end
end
o = BaseClass.new
o.extend ChildModule
o.behavior
and it outputs as follows (with ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-linux])
base class behavior
t.rb:9:in `behavior': undefined method `behavior' for nil:NilClass (NoMethodError)
from t.rb:16:in `<main>'
My guess is that I can not use super, as super does not exist in the module. But it prints out that line from the super method, is that strange?
How do I achieve it what I want above?
Answer by #davidrac is working, however being more curious, would like to know, how can I get a handle to base class instance? Say for instance I added the following method to BaseClass
def behavior2
puts 'base class behavior2'
end
and overrides it in ChildModule. Now from ChildModule behavior can I make a call to behavior2 of BaseModule?
I think the correct syntax is:
module ChildModule
def behavior
super
puts 'child module behavior'
end
end

Ruby instance_eval confusion with load`

I am trying to test a single method in ruby. It is in a separate file so basically:
a.rb:
def my_method
...
end
in my a_spec.rb
require 'minitest/autorun'
Object.instance_eval do
load("path_to/a.rb")
def hello_world
...
end
end
When I try to run my test, it says that my_method is a private method while I can actually call Object.hello_world outright. What gives?
Also, is there an easier way to test plain ruby methods(no classes or modules) with minitest?
Doing the load above doesn't add the methods of a.rb as singleton methods to Object. Rather, it adds the methods to the global namespace. (The fact that you are doing the load inside the block where self refers to the Object class is irrelevant.)
With you above code, you should be able to call *my_method* directly in your tests:
class MyTest < MiniTest::Unit::TestCase
def test_my_method
assert my_method
end
def test_hello_world
assert Object.hello_world
end
end

Resources