Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing Private Methods from Module Functions in Ruby

Tags:

module

ruby

I'm trying to create private helper methods for module functions to no avail. I feel like there's something very simple I'm missing.

Updated example with a more understandable use case:

module FancyScorer
  module_function

  def score(ary)
    scores = []
    ary.each_slice(2).with_index do |slice, i|
      scores <<
        case i % 2
        when 0
          score_eventh(ary)
        else
          score_oddth(ary)
        end
    end
    scores.inject(:+)
  end

  private

  def score_eventh(ary)
    ary.inject(:+) / (ary.size - 1)
  end

  def score_oddth(ary)
    ary.inject(:*) / (ary.size - 1)
  end
end

FancyScorer.score([1,2,3,4])
# => `block in score_curiously': undefined method `score_eventh'
#    for FancyScorer:Module (NoMethodError)

Note: The private methods should remain private.

Here's the use case: there are several modules that contain various scoring techniques, e.g. FancyScorer, SimpleScorer, ComplexScorer. These functions are tested independently, and then are employed to create a score method for different classes. For instance:

class A
  ...
  def score    
    FancyScorer.score(metrics) + 2*SimpleScorer.score(metrics)
  end
end

class B
  ...
  def score
    0.5*FancyScorer.score(metrics) + 2*SimpleScorer.score(metrics[0,3]) + ComplexScorer.score(metrics)
  end
end

Previous example with no provided use case:

module Party
  module_function

  def pooper
    enjoy
  end

  private

  def enjoy
    puts "Wahoo!"
  end
end

Party.pooper
# => NameError: undefined local variable or method `enjoy' for Party:module
#            from (party): in `pooper`
like image 313
fny Avatar asked Aug 29 '13 18:08

fny


1 Answers

module_function(symbol, ...) → self says:-

Creates module functions for the named methods. These functions may be called with the module as a receiver, and also become available as instance methods to classes that mix in the module. Module functions are copies of the original, and so may be changed independently. The instance-method versions are made private. If used with no arguments, subsequently defined methods become module functions.

You need to make sure you declare the helper method as a #private_class_method: private only affects instance methods.

module Party

  def enjoy
    puts 'hello'
  end

  def pooper
    enjoy
  end

  private_class_method :enjoy
  module_function :pooper
end

Party.pooper # => 'hello'
Party.enjoy # => private method `enjoy' called for Party:Module (NoMethodError)

Furthermore, you should be careful about the ordering of accessibility keywords like public, private, and module_function. These do not overlap, but rather override.

module Party
  module_function # Module function declarations begin
  def pooper
    bar
  end
  def bar
    enjoy
  end

  private         # Private declarations begin, module function declarations end
  module_function # Private declarations end, module function declarations begin
  def enjoy       # Therefore, this is a module function
    "Wahoo!"
  end
end

Party.pooper # => "Wahoo!"
Party.bar # => "Wahoo!"
Party.enjoy # => "Wahoo!"  <-- No longer private

Note here module_function overrides the previous private declaration.


Here some more examples when module_function will do its job, and when not.

The module_function definitions stop once another accessibility keyword pops up. In this example, module_function is interrupted by public making #pooper a public instance method. Use of private would similarly block module_method.

module Party
  module_function
  public
  def pooper
    "i am pooper"
  end
end

Party.pooper
# undefined method `pooper' for Party:Module (NoMethodError)

Now if the order is changed:

module Party
  public
  module_function
  def pooper
    "i am pooper"
  end
end

Party.pooper # => "i am pooper"
like image 186
Arup Rakshit Avatar answered Oct 11 '22 14:10

Arup Rakshit