120

I have a two part question

Best-Practice

  • I have an algorithm that performs some operation on a data structure using the public interface
  • It is currently a module with numerous static methods, all private except for the one public interface method.
  • There is one instance variable that needs to be shared among all the methods.

These are the options I can see, which is the best?:

  • Module with static ('module' in ruby) methods
  • Class with static methods
  • Mixin module for inclusion into the data structure
  • Refactor out the part of the algorithm that modifies that data structure (very small) and make that a mixin that calls the static methods of the algorithm module

Technical part

Is there any way to make a private Module method?

module Thing
  def self.pub; puts "Public method"; end
  private
  def self.priv; puts "Private method"; end
end

The private in there doesn't seem to have any effect, I can still call Thing.priv without issue.

Eric Platon
  • 9,819
  • 6
  • 41
  • 48
Daniel Beardsley
  • 19,907
  • 21
  • 66
  • 79
  • 5
    FYI there's no such thing as a 'static' method in ruby, they're called class instance methods – brad Jan 12 '11 at 20:00
  • 37
    An old comment, but as it has four upvotes, I must point out that there's no such thing as a 'class instance method'. 'Class method' is the correct term. – micapam Mar 14 '13 at 23:34
  • 5
    `private` only affects instance methods, not class methods. use `private_class_method` instead: `module Thing; def self.pub; end; private_class_method :pub; end` – apeiros Jan 14 '14 at 16:12
  • 3
    @micapam Class instance methods do exist in Ruby, and they’re different from class methods. – Marnen Laibow-Koser Jun 24 '20 at 14:26
  • 2
    @micapam, some would argue there are no "class methods" either, that "class methods" is just slang for "singleton class instance methods". – Cary Swoveland Apr 26 '22 at 18:33

10 Answers10

100

I think the best way (and mostly how existing libs are written) to do this is by creating a class within the module that deals with all the logic, and the module just provides a convenient method, e.g.

module GTranslate
  class Translator
    def perform(text)
      translate(text)
    end

    private

    def translate(text)
      # do some private stuff here
    end
  end

  def self.translate(text)
    t = Translator.new
    t.perform(text)
  end
end
Sebastián Palma
  • 32,692
  • 6
  • 40
  • 59
ucron
  • 2,822
  • 2
  • 18
  • 6
  • 14
    Ruby newb here. In this example, is the Translator class exposed as part of the public interface of the module? Can the 'perform' method have its access restricted to GTranslate? – rshepherd Jul 23 '11 at 20:46
  • 2
    @rshepherd The `perform` is not the method that is supposed to be private here, the private method is the private method in the `Translator` class (@ucron's example does not have any, what is very unfortunate). `GTranslate.translate` is only a convenient method for `GTranslate::Translator#perform`, there is no real gain concealing it, if it was at all possible. – michelpm Apr 24 '13 at 10:51
  • 31
    I'm not sure what's achieved by having a class here. If the goal is to have a private module method, then this doesn't meet the goal. Because you can access the "perform" method from outside the module by calling GTranslate::Translator.new.perform. In otherwords, it is not private. – Zack Xu Jun 26 '13 at 16:07
  • It is, since you cannot call this private method from the outside *directly*. You can do it only by calling the public `perform` method and letting it call the private method indirectly. (Of course whether it calls some private method or not, is not your business, it's an implementation detail. It can be changed later by the `Translator` class's programmer and shouldn't affect your code at all.) – SasQ Sep 02 '13 at 13:54
  • Just adding the word private above the self.translate( text ) method does the trick. You can verify this by running GTranslate.private_instance_methods to see this afterwards. – jschorr Feb 10 '14 at 23:24
  • 1
    @jschorr I think the Op and this answer intend to make a private class or module method, not an instance method. Also, that won't make any instance method private as `self.translate` declares a class/module method. – konsolebox Sep 16 '14 at 10:02
  • 1
    @SasQ *It is, since you cannot call this private method from the outside directly. You can do it only by calling the public perform method and letting it call the private method indirectly.* - That *private* method you're mentioning is not a private method no matter how you look at it. You're actually giving an incomplete concept-based solution, not a true virtual solution. And I actually find this solution expensive. – konsolebox Sep 16 '14 at 10:07
  • 7
    `GTranslate::Translator.new.perform(text)` — convoluted, but not private! – abhillman Oct 14 '16 at 19:07
  • @ZackXu The intent is to not clutter the class or module that includes this module. Yes you can instantiate the *Translator* and call the methods yourself, however if this is not part of your public API you might want to leave that responsibility with the module since the instance behaviour might change. You can compare this with a car. When you open the hood you might be able to fiddle around with the components, but when you actually want to drive you should let the car take care of those things by using the provided interface (steering wheel, gas paddle, etc.). – 3limin4t0r Dec 03 '18 at 10:31
91

There's also Module.private_class_method, which arguably expresses more intent.

module Foo
  def self.included(base)
    base.instance_eval do
      def method_name
        # ...
      end
      private_class_method :method_name
    end
  end
end

For the code in the question:

module Thing
  def self.pub; puts "Public method"; end
  def self.priv; puts "Private method"; end
  private_class_method :priv
end

Ruby 2.1 or newer:

module Thing
  def self.pub; puts "Public method"; end
  private_class_method def self.priv; puts "Private method"; end
end
konsolebox
  • 72,135
  • 12
  • 99
  • 105
  • I wasn't aware of this. Will it work before the method definition too, like `private`? – Marnen Laibow-Koser Oct 25 '11 at 14:08
  • 7
    This answer along with [@JCooper's answer](http://stackoverflow.com/a/318893/445221) is the real solution. @MarnenLaibow-Koser It doesn't. You can consider the other answer at the cost of more grouping and indentations. It may actually be the preferred solution to some. (Replying just for the sake of reference.) – konsolebox Sep 16 '14 at 10:24
77
module Writer
  class << self
    def output(s)
      puts upcase(s)
    end

    private

    def upcase(s)
      s.upcase
    end
  end
end

Writer.output "Hello World"
# -> HELLO WORLD

Writer.upcase "Hello World"
# -> so.rb:16:in `<main>': private method `upcase' called for Writer:Module (NoMethodError)
cdrev
  • 5,750
  • 4
  • 20
  • 27
29

You can use the "included" method to do fancy things when a module is mixed in. This does about what you want I think:

module Foo
  def self.included(base)
    class << base 
      def public_method
        puts "public method"
      end
      def call_private
        private_method
      end
      private
      def private_method
        puts "private"
      end
    end
  end
end

class Bar
  include Foo
end

Bar.public_method

begin
  Bar.private_method
rescue
  puts "couldn't call private method"
end

Bar.call_private
Cameron Price
  • 1,185
  • 8
  • 14
11

Unfortunately, private only applies to instance methods. The general way to get private "static" methods in a class is to do something like:

class << self
  private

  def foo()
   ....
  end
end

Admittedly I haven't played with doing this in modules.

J Cooper
  • 16,891
  • 12
  • 65
  • 110
  • 7
    This is not true. You can have private class methods and private module methods. – mikeycgto Jun 18 '12 at 23:46
  • You can have private class methods, but just doing this won't make `.foo` a private class method: "private; def self.foo()" – Ari Aug 21 '13 at 20:16
  • @mikeycgto Care to elaborate the difference between private class methods and private module methods? Because I think they're just the same. Note that both `private` and `private_class_method` are owned by `Module` not `Class`. This code works by the way and it is the alternative to using `private_class_method`. – konsolebox Sep 16 '14 at 09:51
4

This method won't allow sharing data with the private methods unless you explicitly pass the data by method parameters.

module Thing
  extend self

  def pub
    puts priv(123)
  end

  private
  
  def priv(value)
    puts "Private method with value #{value}"
  end
end

Thing.pub
# "Private method with value 123"

Thing.priv
# NoMethodError (private method `priv' called for Thing:Module)
Gerry Shaw
  • 9,178
  • 5
  • 41
  • 45
3

A nice way is like this

module MyModule
  class << self
    def public_method
      # you may call the private method here
      tmp = private_method
      :public
    end

    private def private_method
      :private
    end
  end
end

# calling from outside the module
puts MyModule::public_method
Tallak Tveide
  • 351
  • 1
  • 4
1

What's about storing methods as lambdas within class variables/constants?

module MyModule
  @@my_secret_method = lambda {
    # ...
  }
  # ...
end

For test:
UPD: huge update of this code after 6 years shows cleaner way to declare private method d

module A
  @@L = lambda{ "@@L" }
  def self.a ; @@L[] ; end
  def self.b ; a ; end

  class << self
    def c ; @@L[] ; end
    private
    def d ; @@L[] ; end
  end
  def self.e ; c ; end
  def self.f ; self.c ; end
  def self.g ; d ; end
  def self.h ; self.d ; end

  private
  def self.i ; @@L[] ; end
  class << self
    def j ; @@L[] ; end
  end

  public
  def self.k ; i ; end
  def self.l ; self.i ; end
  def self.m ; j ; end
  def self.n ; self.j ; end
end

for expr in %w{ A.a A.b A.c A.d A.e A.f A.g A.h A.i A.j A.k A.l A.m A.n }
  puts "#{expr} => #{begin ; eval expr ; rescue => e ; e ; end}"
end

Here we see that:

A.a => @@L
A.b => @@L
A.c => @@L
A.d => private method `d' called for A:Module
A.e => @@L
A.f => @@L
A.g => @@L
A.h => private method `d' called for A:Module
A.i => @@L
A.j => @@L
A.k => @@L
A.l => @@L
A.m => @@L
A.n => @@L

1) @@L can not be accesses from outside but is accessible from almost everywhere
2) class << self ; private ; def successfully makes the method d inaccessible from outside and from inside with self. but not without it -- this is weird
3) private ; self. and private ; class << self do not make methods private -- they are accessible both with and without self.

Nakilon
  • 34,866
  • 14
  • 107
  • 142
0

Make a private module or class

Constants are never private. However, it's possible to create a module or class without assigning it to a constant.

So an alternative to :private_class_method is to create a private module or class and define public methods on it.

module PublicModule
  def self.do_stuff(input)
    @private_implementation.do_stuff(input)
  end

  @private_implementation = Module.new do
    def self.do_stuff(input)
      input.upcase # or call other methods on module
    end
  end
end

Usage:

PublicModule.do_stuff("whatever") # => "WHATEVER"

See the docs for Module.new and Class.new.

Nathan Long
  • 122,748
  • 97
  • 336
  • 451
  • I really like this method. But it doesn't seem possible to remove the `.self` in the method definitions, include it in another class, and use them as instance_methods of the including class. Do you know if there is any way to make it work? – Shiyason Mar 10 '17 at 09:43
0

Here's a solution for how you can have multiple classes nested within a single module, with the ability to call a private method on the module that's accessible from any of the nested classes, by making use of extend:

module SomeModule

  class ClassThatDoesNotExtendTheModule
    class << self
      def random_class_method
        private_class_on_module
      end
    end
  end

  class ClassThatDoesExtendTheModule
    extend SomeModule
  
    class << self
      def random_class_method
        private_class_on_module
      end
    end
  end

  class AnotherClassThatDoesExtendTheModule
    extend SomeModule
  
    class << self
      def random_class_method
        private_class_on_module
      end
    end
  end

  private

  def private_class_on_module
    puts 'some private class was called'
  end
  
end

Some output to show the solution in action:

> SomeModule::ClassThatDoesNotExtendTheModule.random_class_method

NameError: undefined local variable or method `private_class_on_module' for SomeModule::ClassThatDoesNotExtendTheModule:Class


> SomeModule::ClassThatDoesExtendTheModule.random_class_method

some private class was called


> SomeModule::ClassThatDoesExtendTheModule.private_class_on_module

NoMethodError: private method `private_class_on_module' called for SomeModule::ClassThatDoesExtendTheModule:Class


> SomeModule::AnotherClassThatDoesExtendTheModule.random_class_method

some private class was called


> SomeModule::AnotherClassThatDoesExtendTheModule.random_class_method

NoMethodError: private method `private_class_on_module' called for SomeModule::AnotherClassThatDoesExtendTheModule:Class
jeffdill2
  • 3,968
  • 2
  • 30
  • 47