Method visibility in Ruby (public, protected, and private methods) has been well explained in places like this blog post. But in Ruby on Rails it seems slightly different than it would be in a regular Ruby application because of the way the framework is set up. So, in Rails models, controllers, helpers, tests, etc., when is/isn't it appropriate to use protected or private methods?
Edit: Thanks for the answers so far. I understand the concept of protected and private in Ruby, but I'm looking more for an explanation of the typical way those types of visibility are used within the context of the various pieces of a Rails app (models, controllers, helpers, tests). For example, public controller methods are action methods, protected methods in the application controller are used for "helper methods" that need to be accessed by multiple controllers, etc.
For models, the idea is that the public methods are the public interface of the class. Public methods are intended to be used by other objects, while protected/private methods are to be hidden from the outside.
This is the same practice as in other object-oriented languages.
For controllers and tests, just do as you please. Both controller and test classes are only instantiated and called by the framework (yes, I know you can theoretically get the controller from the view, but if you do that, something is strange anyway). Since no one will ever create those things directly, there's nothing to "protect" against.
Addendum/Correction: For controllers, you should mark the "helper" methods as protected private, and only the actions themselves should be public. The framework will never route any incoming HTTP calls to actions/methods that are not public, so your helper methods should be protected in that way.
For helpers it will make no difference if a method is protected or private, since they are always called "directly".
You can mark stuff protected in all those cases if it makes things easier for you to understand, of course.
You use a private method if you want no one else but self
to use a method. You use a protected method if you want something only self and is_a?(self)
s can call.
A good use of protected might be if you had a "virtual" initialization method.
class Base
def initialize()
set_defaults()
#other stuff
end
protected
def set_defaults()
# defaults for this type
@foo = 7
calculate_and_set_baz()
end
private
def calculate_and_set_baz()
@baz = "Something that only base classes have like a file handle or resource"
end
end
class Derived < Base
protected
def set_defaults()
@foo = 13
end
end
@foo will have different values. and the Derived instances will not have @baz
Update:
Since I wrote this, some things have changed in Ruby 2.0+ Aaron Patterson has an excellent write up http://tenderlovemaking.com/2012/09/07/protected-methods-and-ruby-2-0.html
The difference between protected and
private is subtle. If a method is
protected, it may be called by any
instance of the defining class or its
subclasses. If a method is private, it
may be called only within the context
of the calling object---it is never
possible to access another object
instance's private methods directly,
even if the object is of the same
class as the caller. For protected
methods, they are accessible from
objects of the same class (or
children).
http://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Classes#Declaring_Visibility
You seem to have a good idea of the semantics of class visibility (public/protected/private) as applied to methods. All I can offer is a quick outline of the way I implement it in my Rails apps.
I implement protected methods in the base application controller so they can get called by any controller via filters (e.g. before_filter :method_foo). In a similar way, I define protected methods for models that I want to use in all of them in a base model that they all inherit from.
Although actions need to be public methods of a controller, not all public methods are necessarily actions. You can use hide_action
if you're using a catch-all route like /:controller/:action/:id
or if it's disabled (the default in Rails 3) then only methods with explicit routes will be called.
This can be useful if you're passing the controller instance to some other library like the Liquid template engine as you can provide a public interface rather than having to use send in your Liquid filters and tags.