NoMethod error despite that the method exists

I created a class with the method two_fer (line# 6) in it but am still getting ‘NoMethod’ error. Perhaps someone can spot something I missed?
Thanks a lot!

user@debian:~/programming/exercism.org/ruby/two-fer$ ruby --verbose -r minitest/pride two_fer_test.rb 
Run options: --seed 49602

# Running:

SSE

Error:
TwoFerTest#test_no_name_given:
NoMethodError: undefined method `two_fer' for TwoFer:Class

    assert_equal "One for you, one for me.", TwoFer.two_fer
                                                   ^^^^^^^^
    two_fer_test.rb:7:in `test_no_name_given'


rails test two_fer_test.rb:5



Finished in 0.004945s, 606.6500 runs/s, 0.0000 assertions/s.
3 runs, 0 assertions, 0 failures, 1 errors, 2 skips

You have skipped tests. Run with --verbose for details.
user@debian:~/programming/exercism.org/ruby/two-fer$ 
user@debian:~/programming/exercism.org/ruby/two-fer$ cat -n two_fer.rb 
     1  class TwoFer
     2    #def initialize(name)
     3    #  @name=name
     4    #end
     5
     6    def two_fer(name=nil)
     7      if name
     8        p "One for #{name}, one for me."
     9      else
    10        p "One for you, one for me."
    11      end
    12    end
    13  end
user@debian:~/programming/exercism.org/ruby/two-fer$ 
user@debian:~/programming/exercism.org/ruby/two-fer$ cat -n two_fer_test.rb 
     1  require 'minitest/autorun'
     2  require_relative 'two_fer'
     3
     4  class TwoFerTest < Minitest::Test
     5    def test_no_name_given
     6      # skip
     7      assert_equal "One for you, one for me.", TwoFer.two_fer
     8    end
     9
    10    def test_a_name_given
    11      skip
    12      assert_equal "One for Alice, one for me.", TwoFer.two_fer("Alice")
    13    end
    14
    15    def test_another_name_given
    16      skip
    17      assert_equal "One for Bob, one for me.", TwoFer.two_fer("Bob")
    18    end
    19  end

Hi turtle,
I believe in this case you need to use an instance method,
so def self.two_fer(...)

As turtle666 has written it, it is already an instance method. It needs to be a class method. Note that the test has TwoFer.two_fer, not TwoFer.new.two_fer

At the point where Ruby is interpreting that def, self is the class.

As you may know, the invocation of a method in Ruby—unlike with some other languages—happens by sending a message to an object (at least conceptually).

You are correct!

However, as you probably know by now, Ruby provides two different meanings of the idea of having a method in a class.

With one way, you can send a message to an instance of the class. And that instance will be the receiver of the message. (The message will include the method name.) This works for instance methods.

With the other way, you can send a message directly—instead—to the class! That’s because the class itself can be a message receiver. In Ruby, any class exists as an object in its own right. So it can receive messages—just like any other object can. This works for class methods.

By default, most methods are instance methods (the first way).

You have to add something special if you want a class method (the second way).

In your TwoFer (implementation) class, you implicitly defined the two_fer method as an instance method.

However, in your TwoFerTest test class, you are sending the two_fer message directly to the TwoFer class. That’s the meaning of your syntax, TwoFer.two_fer. Any object written immediately before the dot indicates the receiver (where the message goes).

In what you wrote, that object (before the dot) is the TwoFer class itself. And currently, that object doesn’t have a two_fer method—only its instances do.

To define a class method, there are many ways.

Perhaps the simplest is to write self as the explicit receiver (sort of) of the definition message. This works because self inside any class definition is a reference to that class’s object. For instance, writing def self.two_fer(name=nil) within the definition of the TwoFer class should make two_fer be one of its class methods.

1 Like

Welcome @MarkDBlackwell !

Thank you all for the wonderful comments!

The explanations by @MarkDBlackwell and @Georgy5 made a lot of sense, though admittedly it made my head spin, lol and am still recuperating from it.

This site is awesome.

One more question corollary to this, does including the def initialize(name) a good practice in this scenario?

Do note that providing a default of nil will exclude that as a valid given name, though, so it does not strictly adhere to “use the name given” in the directions. Instead, the default name of 'you' should be used as the default value, which avoids excluding nil as a name that might be given.

But hopefully this comes out in the mentoring session itself!

1 Like

initialize is one of the few exceptional methods.

Its name gets changed automatically to new, and it becomes a class method.

If we invoke new on the class, then this executes the code within initialize while each instance is being created.

If your scenario excludes instances of that class—if you never invoke TwoFer.new—then the code in initialize will never be executed, so it can be omitted.

If you later decide to have instances of TwoFer, then you can use initialize to set them up.

The class method new does not get changed, but it uses allocate and initialize.

And the initialize method is not really exceptional. It is a private method that is inherited from parent of the class that was written.

You would usually not use initialize directly, instead preferring to use it via the class method new.

@kotp 's reply is indeed a very good one for the following reasons:

  • the if-block is no longer necessary
  • and I was able to get away with just the print statement and it covers everything

Here’s the modified code:

user@debian:~/programming/exercism.org/ruby/two-fer$ cat -n two_fer.rb 
     1  class TwoFer
     2
     3    def self.two_fer(name='you')
     4      #if name
     5        p "One for #{name}, one for me."
     6      #else
     7      #  p "One for you, one for me."
     8      #end
     9    end
    10  end

I’m sure you are right!

You don’t need the p either as there’s no need to print anything in this exercise - you just need the method to return things :slight_smile:

Not that this is a mentoring session, but the p is not a print method. It is a debugging tool, though, and returns its argument as well as using the inspect method to give the developer friendly hints, if any, to provide syntax indicators.

The methods puts and print both return nil, and has the side effect of output, through the mechanisms provided by Ruby.

This method is not expected to have any output, the tests are looking for the return of the method.

That is indeed the case, I removed p and it still worked. I even changed it from class to module. I can’t imagine how much value we can extract/improve things if many people are looking into it.

     1  module TwoFer
     2
     3    def self.two_fer(name='you')
     4      "One for #{name}, one for me."
     5    end
     6  end

If you request mentoring on the track for this exercise, I will pick it up, and will talk about the role of initialize where it may be a good idea.

Good idea. I’ve just submitted it for mentoring. Thanks @kotp .

I think too many people think “Oh, simple exercise nothing left to learn about the language here”. But that is not true at all.