Ruby Private Class Methods

Ruby Private Class Methods

In the Ruby programming language, defined methods come in two variants: instance methods and class methods. Instance methods are available after an object has been initialized, creating an instance. Class methods, on the other hand, are available without creating an instance of the class they are defined upon.

Ruby methods can vary in visibility. Public methods are available in any context, while private methods’ availability is restricted within the instance of a class and its descendants. A third visibility scope, protected, behaves similarly to private methods, but protected methods can be called by other instances of the same class.

For a quick refresher, public and private instance methods look like this:

class Dog
  def do_trick
    bark
  end

  private

  def bark
    puts 'woof woof'
  end
end

When the public method is called:

> dog = Dog.new
> dog.do_trick
# => woof woof

And the private method:

> dog = Dog.new
> dog.bark
# => NoMethodError: private method `bark' called for <Dog>

Private class methods might not be as common as private instance methods, but they still have their place. For instance, a class method may require internal helper methods to complete its function. Whatever the reason, defining private class methods has value but is not always intuitive.

This example Dog class needs to maintain a list of tricks that will be used within the other public class methods. This list should not be accessible to any callers outside the Dog class.

The wrong way

A first pass at writing the private tricks method could look like:

class Dog
  private

  def self.tricks
    [:bark, :roll_over, :fetch]
  end
end

However, when testing the visibility of the tricks method:

> Dog.tricks
# => [:bark, :roll_over, :fetch]

Uh oh, no error was thrown indicating a that a private method was called, this method is completely public.

Why?

The reason that the above code did not produce a private method has to do with Ruby’s object hierarchy, interactions amongst internal classes, instances of those classes, and eigenclasses. A detailed write up about how Ruby’s objects work with one another can be found in a previous post.

When defining methods in a class, the default context and the context introduced by the self. method declaration are distinct.

The private method scope can not be used in the above way as it does not handle the context change between methods defined on the Dog class and defined within self context.

So what are the alternatives?

1. class << self

One alternative way to define class methods on an object is to use the class << self syntax. This syntax opens an eigenclass for the supplied argument. In this example, self within the Dog class will open the Dog's eigenclass.

class Dog
  class << self
    private

    def tricks
      [:bark, :roll_over, :fetch]
    end
  end
end

One thing to note is that when defining methods like this, the declaration has changed from def self.tricks to simply def tricks.

Now, the private scope is preserved and expected behaviour is achieved:

> Dog.tricks
# => NoMethodError: private method `tricks' called for Dog:Class

2. Modules

Modules in ruby are collections of methods and constants. These collections can be used as encapsulation tools or, in this case, alternatives to defining public and private class methods.

When a class extends a module, all the methods within that module become class methods on the subject class*. This pattern can be used to define the tricks method on Dog.

*Note: This only pertains to methods defined in a “typical sense” any def self. or def Dog. method definitions in a module do not automatically become class methods in the same way when extended.

class Dog
  module ClassMethods
    private

    def tricks
      [:bark, :roll_over, :fetch]
    end
  end

  extend ClassMethods
end

> Dog.tricks
# => NoMethodError: private method `tricks' called for Dog:Class

A benefit of this approach is readability. The module named ClassMethods, which is contained and thus encapsulated by the Dog class, is the clear home for all desired class methods.

Visibility modifiers like private behave accordingly and a simple extend at the bottom of the module definition brings it all together.

3. private_class_method

A third approach is to use the built in method Module#private_class_method. This method’s purpose is to change the visibility of existing class methods.

Unlike the other two solutions, this one does not require a different style of method definition from the incorrect solution:

class Dog
  def self.tricks
    [:bark, :roll_over, :fetch]
  end

  private_class_method :tricks
end

> Dog.tricks
# => NoMethodError: private method `tricks' called for Dog:Class

This method can also be in-lined during the class’ method definition:

class Dog
  private_class_method def self.tricks
    [:bark, :roll_over, :fetch]
  end
end

> Dog.tricks
# => NoMethodError: private method `tricks' called for Dog:Class

If a single class method must be private and saving lines is very important, this style might be applicable; but, it certainly sacrifices a level of readability.

A major strength of private_class_method is its explicitness. Unlike the other approaches, this one does not require a special module definition or method definition context switching.

Further Reading

The aforementioned solutions will get the job done but might not answer lingering questions about why things work the way they do. A few great articles on the ruby eigenclass and Matz’s thoughts on Ruby method design should help paint a more concise picture of why Ruby’s private class method definition is complex.