Ruby Private Class Methods
24 Jan 2016In 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.