Ruby newbies often get confused when they learn about method access levels. Other languages use the same denomination (notably PHP and Java) but with slightly different meaning, so let’s clarify the use of public
, private
and protected
with some examples.
Public
Let’s start from the basics: by default methods are defined as public, so you don’t need to explicitly type that keyword inside your class:
class SshKeyPair
def id_rsa_pub
'I am the public key'
end
end
pair = SshKeyPair.new
c.id_rsa_pub
# => "I am the public key"
Let’s use ruby introspection to confirm that the method has been added to the object methods list:
pair.methods.include? :id_rsa_pub
# => true
pair.public_methods.include? :id_rsa_pub
# => true
Private
Private methods can be accessed only without receiver, practically this means that only the very same instance can access them, and only when the self receiver is omitted:
class SshKeyPair
private
def id_rsa
'I am the private key'
end
end
pair.id_rsa
# NoMethodError: private method `id_rsa' called for #<SshKeyPair:0x007f87f139be28>
pair.methods.include? :id_rsa
# => false
pair.private_methods.include? :id_rsa
# => true
pair.protected_methods.include? :id_rsa
# => false
Of course you can always use send
to circumvent restrictions (but remember that probably that method was declared private for good reason):
pair.send :id_rsa
# => "I am the private key"
Now let’s see the different behavior of the method when called with and without self
directly inside the class:
class SshKeyPair
def leak_private_key
id_rsa
end
def lousy_leak_private_key
self.id_rsa
end
end
pair.leak_private_ley
# => "hello, privately"
pair.lousy_leak_private_key
NoMethodError: private method `id_rsa' called for #<SshKeyPair:0x007f87f139be28>
There’s one exception: writer methods. When declared private, they still can be used inside the class using self:
class Album
def set_name(name)
self.name = name
end
private
attr_accessor :name
end
album = Album.new
# => #<Album:0x007fb439125328>
album.set_name 'Pet Sounds'
# => "Pet Sounds"
album.name
NoMethodError: private method `name' called for #<Album:0x007fb439125328 @name="Pet Sounds">
album.send :name
# => "Pet Sounds"
album.name = 'Smile'
NoMethodError: private method `name=' called for #<Album:0x007fb439125328 @name="Pet Sounds">
I think this happens because there is now way to use a writer method without making self
explicit.
Protected
protected
is probably the most misunderstood in the group: it works like private
, but the method is visible also by instances of the same class:
class Woman
attr_accessor :age
protected :age
end
kate = Woman.new
kate.age
# NoMethodError: protected method `age' called for #<Woman:0x007fb439112340>
kate.protected_methods.include? :age
# => true
kate.private_methods.include? :age
# => false
kate.methods.include? :age
# => true
This makes sense when instance should be allowed to interact with each other while not exposing their behaviour publicly. Let’s enhance our Woman class so that it allows age comparisons keeping the age secret:
class Woman
def older?(other)
(age <=> other.age) == 1
end
end
mother = Woman.new
daughter = Woman.new
mother.age = 35
daughter.age = 10
mother.older? daughter
# => true
But what happens with subclasses? Will it still work? Of course it does:
class Girl < Woman
def age=(value)
raise 'Girls older than 20 years are women!' if value > 20
@age = value
end
end
daughter = Girl.new
daughter.age = 10
daughter.older? mother
# => false
Statements can be mixed and repeated freely to suit your tastes:
class C
private
def a; end
public
def b; end
protected
def c; end
private
def d; end
end
end
or you can change the method visibility on second thought:
class C
def a; end
end
class C
private :a
end
Ruby 2.1 additions
The recently released Ruby 2.1 introduced a few changes that affect method access level keywords: basically, they’re no more simple keywords but full-fledged methods, so they can be redefined:
class C def self.private(*m_names) m_names.each {|m_name| puts "#{m_name} is now a private method."} end end class C def a; end private :a end # a is now a private method.
The second change is that method definitions with def
now return the method name symbol, so we can write:
class C private def b; end end # b is now a private method.
Leave a Reply