Are Your Class Methods Actually Private?
While no methods in Ruby are really safe, even when private, it's possible the class methods you think are private aren't actually private.
Private Instance Methods
To make an instance method private you call the private
method. This can be done in two ways.
On it's own line
You can put the private
method on its own line. When you do this, everything after it will be private.
class Pizza
private
def remaining_slices
puts "I'm not sharing."
end
def hide
puts "These aren't the pizzas you're looking for."
end
end
In this case both remaining_slices
and hide
will be private since they follow the call to the private
method.
>> pizza = Pizza.new #=> #<Pizza:0x00000102988e58>
>> pizza.remaining_slices
NoMethodError: private method 'remaining_slices' called for #<Pizza:0x00000102988e58>
>> pizza.hide
NoMethodError: private method 'hide' called for #<Pizza:0x00000102988e58>
Prefix a Method Definition
You can also prefix specific method definitions with the private
method. This will make only that method private.
class Pizza
private def remaining_slices
puts "I'm not sharing."
end
def hide
puts "These aren't the pizzas you're looking for."
end
end
If we create a new instance of our pizza class
>> pizza = Pizza.new #=> #<Pizza:0x00000101346168>
Our remaining_slices
method will still be private
>> pizza.remaining_slices
NoMethodError: private method 'remaining_slices' called for #<Pizza:0x00000101346168>
but now our hide
method is public, even though it comes after the private definition of reamaining_slices
>> pizza.hide
"These aren't the pizzas you're looking for."
Private Class Methods
Now, it seems like defining a private class method should be the same.
Let's change our Pizza
class definition to call private when defining a class method:
class Pizza
private
def self.make_special_recipe
puts 'A wonderful combination of cheese, carbs, and love'
end
end
Now when we try to call Pizza.make_special_recipe
we should receive a NoMethodError
like before, right?
>> Pizza.make_special_recipe
"A wonderful combination of cheese, carbs, and love"
It looks like we are able to call the class method, even though we thought we made it private.
If we dig around in the documentation we find that there is actually another private-like method that deals with classes, the not so subtley named, private_class_method
method.
We can now update our class definition to prefix our class method definition with our new found private_class_method
method
class Pizza
private_class_method def self.make_special_recipe
puts 'A wonderful combination of cheese, carbs, and love'
end
end
and now when we try to call our class method we receive the expected NoMethodError
>> Pizza.make_special_recipe
NoMethodError: private method 'make_special_recipe' called for Pizza:Class
Multiple Methods
So private_class_method
works the same as private
, just on class methods, right?
If so, we should be able to put it on its own line, and everything below will be private.
class Pizza
private_class_method
def self.make_special_recipe
puts 'A wonderful combination of cheese, carbs, and love'
end
def self.another_private_method
puts "You can't see me"
end
end
>> Pizza.make_special_recipe
"A wonderful combination of cheese, carbs, and love"
>> Pizza.another_private_method
"You can't see me"
Now neither method is private!
The issue is private_class_method
expects to take arguments, symbols, representing the names of methods it is to make private.
This worked in our first example because, when defining a method, Ruby returns that method's name as a sybol
>> def foo; end #=> :foo
so
private_class_method def self.make_special_recipe
puts 'A wonderful combination of cheese, carbs, and love'
end
was equivalent to
method_name = def self.make_special_recipe
puts 'A wonderful combination of cheese, carbs, and love'
end
private_class_method(method_name)
Notice that the call to private_class_method
has to come after the method is defined. It will not work if it is passed the method name that is not yet defined.
When private_class_method
is on it's own line we are essentially passing in nil
so no class methods are made private.
Now that we undestand how private_class_method
works and why we can't have private_class_method
on its own line, what happens if we pass in multiple methods to make private?
class Pizza
def self.make_special_recipe
puts 'A wonderful combination of cheese, carbs, and love'
end
def self.another_private_method
puts "You can't see me"
end
private_class_method :make_special_recipe, :another_private_method
end
>> Pizza.make_special_recipe
NoMethodError: private method 'make_special_recipe' called for Pizza:Class
>> Pizza.another_private_method
NoMethodError: private method 'another_private_method' called for Pizza:Class
It looks like that works!
We can also pass in the method definitions as arguments and acheieve the same results.
class Pizza
private_class_method(
def self.make_special_recipe
puts 'A wonderful combination of cheese, carbs, and love'
end,
def self.another_private_method
puts "You can't see me"
end
)
end
>> Pizza.make_special_recipe
NoMethodError: private method 'make_special_recipe' called for Pizza:Class
>> Pizza.another_private_method
NoMethodError: private method 'another_private_method' called for Pizza:Class
Alternative Syntax
There is another way we can define private class methods that actually involves the private
method - by using the class << self
syntax.
class Pizza
class << self
private
def make_special_recipe
puts 'A wonderful combination of cheese, carbs, and love'
end
def another_private_method
puts "You can't see me"
end
end
end
just like with our previous solutions, attempting to call these methods on our class will result in NoMethodError
s
>> Pizza.make_special_recipe
NoMethodError: private method 'make_special_recipe' called for Pizza:Class
>> Pizza.another_private_method
NoMethodError: private method 'another_private_method' called for Pizza:Class
Shortly after discovering that private
doesn't work for class methods the same as it does for instance methods, I discovered some code at work where I was making this mistake. In this case I had a few small classes that had all of their functionality defined in class methods. I found making use of the class << self
syntax to be a cleaner solution than using private_class_method
to make them private.
In a previous post I said I wasn't a big fan of this syntax. Since writing that post, I have become more comfortable with the class << self
syntax and have been interested in trying to use it in production code. This scenario was definitely a win for the class << self
syntax in my book.
Lately I have been listening to some of the podcasts produced by Thoughtbot. They bring up their Trello board of ideas they want to test. The ideas are vetted and then it is decided if they should be used company wide. I was speaking with a coworker about my recent work on a project I hadn't worked on in a while and realzied I sort of do the same thing but not as officailly. I can look through various commits and pick out design patterns and recall what I read or watched that inspired me to try something differently. I forsee the split use of class << self
and def self.method
syntaxes to be something I test internally until I settle on one or the other (or have rules for when to use which).
One of the best and worst parts of programming is that there usaually isn't just one solution to a problem. This can be frustrating when you want to just get something done and have it be considered "right". This can also be a lot fun! I like that I can find a use case for a pattern I haven't used before and see if it applies to my current problem. I also like that in as soon as a week I can come back to the same code and hate that pattern because I have some new insight. In order to excel at programing we need to be pushing ourselves, trying new things, and abandoning old things that don't work. We need to always be evolving.