Controlling Access by Making Methods Private

Problem

You've refactored your code (or written it for the first time) and ended up a method that should be marked for internal use only. You want to prevent outside objects from calling such methods.

Solution

Use private as a statement before a method definition, and the method will not be callable from outside the class that defined it. This class defines an initializer, a public method, and a private method:

	class SecretNumber
	 def initialize
	 @secret = rand(20)
	 end
	 def hint
	 puts "The number is #{"not " if secret <= 10}greater than 10."
	 end	

	 
private
	 def secret
	 @secret
	 end
	end

	s = SecretNumber.new
	s.secret
	# NoMethodError: 
private method 'secret' called for
	# #

	s.hint
	# The number is greater than 10.

Unlike in many other programming languages, a private method in Ruby is accessible to subclasses of the class that defines it:

	class LessSecretNumber < SecretNumber
	 def hint
	 lower = secret-rand(10)-1
	 upper = secret+rand(10)+1
	 "The number is somewhere between #{lower} and #{upper}."
	 end
	end

	ls = LessSecretNumber.new
	ls.hint
	# => "The number is somewhere between -3 and 16."
	ls.hint
	# => "The number is somewhere between -1 and 15."
	ls.hint
	# => "The number is somewhere between -2 and 16."

 

Discussion

Like many parts of Ruby that look like special language features, Ruby's privacy keywords are actually methods. In this case, they're methods of Module. When you call private, protected, or public, the current module (remember that a class is just a special kind of module) changes the rules it applies to newly defined methods from that point on.

Most languages that support method privacy make you put a keyword before every method saying whether it's public, private, or protected. In Ruby, the special privacy methods act as toggles. When you call the private keyword, all methods you define after that point are declared as private, until the module definition ends or you call a different privacy method. This makes it easy to group methods of the same privacy levela good, general programming practice:

	class MyClass
	 def public_method1
	 end

	 def public_method2
	 end

	 protected

	 def protected_method1
	 end

	 
private

	 def private_method1
	 end

	 def private_method2
	 end
	end

Private and protected methods work a little differently in Ruby than in most other programming languages. Suppose you have a class called Foo and a subclass SubFoo. In languages like Java, SubFoo has no access to any private methods defined by Foo. As seen in the Solution, Ruby provides no way to hide a class's methods from its subclasses. In this way, Ruby's private works like Java's protected.

Suppose further that you have two instances of the Foo class, A and B. In languages like Java, A and B can call each other's private methods. In Ruby, you need to use a protected method for that. This is the main difference between private and protected methods in Ruby.

In the example below, I try to add another type of hint to the LessSecretNumber class, one that lets you compare the relative magnitudes of two secret numbers. It doesn't work because one LessSecretNumber can't call the private methods of another LessSecretNumber:

	class LessSecretNumber
	 def compare(other)
	 if secret == other.secret
	 comparison = "equal to"
	 else
	 comparison = secret > other.secret ? "greater than" : "less than"
	 end
	 "This secret number is #{comparison} the secret number you passed in."
	 end
	end

	a = LessSecretNumber.new
	b = LessSecretNumber.new
	a.hint
	# => "The number is somewhere between 17 and 22."
	b.hint
	# => "The number is somewhere between 0 and 12."
	a.compare(b)
	# NoMethodError: private method 'secret' called for
	# #

But if I make make the secret method protected instead of private, the compare method starts working. You can change the privacy of a method after the fact by passing its symbol into one of the privacy methods:

	class SecretNumber
	 protected :secret
	end
	a.compare(b)
	# => "This secret number is greater than the secret number you passed in."
	b.compare(a)
	# => "This secret number is less than the secret number you passed in."

Instance variables are always private: accessible by subclasses, but not from other objects, even other objects of the same class. If you want to make an instance variable accessible to the outside, you should define a getter method with the same name as the variable. This method can be either protected or public.

You can trick a class into calling a private method from outside by passing the method's symbol into Object#send (in Ruby 1.8) or Object#funcall (in Ruby 1.9). You'd better have a really good reason for doing this.

	s.send(:secret) # => 19

 

See Also

  • Recipe 8.2, "Managing Class Data," has a pretty good reason for using the Object#send TRick


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net