rubymoduleinclude

Ruby- NoMethodError across modules


I'm having some issues with getting to grips with using modules correctly.

I have:

module Utilities
  def file_search()
    # some code
    return x
  end
end


module Remake_Components
  require 'csv'
  include Utilities

  f = Utilities.file_search()
end

Which gives me the error: #<NoMethodError: undefined method "file_search" for Utilities:Module> I would have expected f to be the result of running the file_search function.

My understanding was I had to use the include keyword to use the functions in the other method but it doesn't seem to be so?


Solution

  • My understanding was I had to use the include keyword to use the functions in the other method but it doesn't seem to be so?

    Module#include will inject the Module into the including Object's hierarchical chain and will make instance methods of the included Module accessible to instances of the including Object.

    In your case file_search is an instance method defined in Utilities and by calling include Utilities you have made this method available to instances of Remake_Components (however Modules do not have instances (per se)).

    The way you are trying to call file_search with the Utilities Module as a receiver does not require a call to include but rather requires the method to be defined as a "class instance method" or a "module function".

    If you would like it to work this way then you have 2 options:

    • Define the method on the eigenclass of Utilities by changing the method declaration to def self.file_search
    • Use the module_function method (not generally a recommended approach) e.g.
    module Utilities
      def file_search 
        # some code
        return x
      end
      module_function :file_search
    end
    

    Both options will allow you to call Utilities.file_search; however, the latter will also allow you to "include" this method as a private instance method when using Module#include.

    By way of Example:

    module A 
      def self.foo = 'Foo'
      def bar = 'Bar'
      module_function :bar
    end 
    
    class B 
      include A 
      def test_foo = A.foo
      def test_bar = bar
    end 
    
    A.foo
    #=> "Foo"
    A.bar 
    #=> "Bar"
    B.new.test_foo
    #=> "Foo"
    B.new.test_bar
    #=> "Bar" 
    B.new.foo
    #=> undefined method `foo' for #<B:0x00007f0c7785e528> (NoMethodError)
    B.new.bar
    #=> private method `bar' called for #<B:0x00007ff5a5dbe5a0> (NoMethodError)
    

    A few other less consequential notes:

    • def file_search() - If there are no arguments the parenthesis can be omitted and as a general style are.
    • return x - ruby will always return the value of the last expression so unless you intend to return a value early the return can be omitted.
    • module Remake_Components - While method names (and local/instance variables) are expressed in lower_snake_case, class and module constants are expressed in UpperCamelCase so this module would generally be named RemakeComponents
    • require 'csv' - require statements do not have a lexical scope so whether or not this is contained within the module declaration has no bearing on its accessibility. For this reason require calls (especially to core libraries) are generally placed at the top of the file (outside of any declaration).