In the previous entry of this series I covered the model part of the MVC support in Rails. The view and controller support is very tightly linked (more so than in, say, Struts) but I’ll start with the controller just to be contrary.
A controller is represented by a class, with each action defined as a separate method. When an action is invoked, an associative array of parameters is then available via the params method on the controller parent class. The decision as to which action to invoke for a given URL is based on the contents of the file config/routes.rb. This contains a series of mappings, the default one being as follows:
1 |
map.connect ':controller/:action/:id' |
This default mapping would mean that a URL of entry/view/123 results in the view method of the EntryController class being invoked with the parameters { :controller => ‘entry’, :action => ‘view’, :id => 123}. For a HTTP POST, the form parameters would also be contained in the array. The file can contain multiple mappins with the first mapping that matches the URL being used. Mappings can also contain constants, have multiple properties, specify default values, and define regular expressions that properties must match. For example, a mapping of:
1 2 3 |
map.connect ':controller/event/:event/entry/:id/:action', :action => 'show', :requirements => { :event => /\d+/ } |
with a URL of admin/event/3/entry/5 would result in the show method being invoked on the AdminController class with the parameters { :controller => ‘admin’, :action => ‘show’, :event => 3, :id => 5}.
The beauty of the routing configuration is that Rails also knows how to navigate it in reverse. So, for example, the AdminController class can call the method url_for with the same parameter set and Rails would generate the shortest URL matching those parameters.
What the controller method does is largely up to you. The default behaviour is that, once the method exits, a template with a name matching the method is rendered. All of the attributes of the controller class are made available to the template. So, for example, the show method from above might simply retrieve a couple of model objects using the parameters from the URL and set them as attributes as follows:
1 2 3 4 |
def show @event = Event.find(params[:event]) @entry = Entry.find(params[:id]) end |
In the next article in this series, you’ll see how the template might then use this information. Alternatively, the controller method might explicitly call one of the variations of the render method to render a different template, return a file or simply return a piece of text, or maybe use the redirect_to method to send back an HTTP redirect.
The controller can also explicitly manipulate cookies but, in most cases, the use of a Rails session will suffice. This is basically an associative array available to the controller that is persisted across requests. The default persistence mechanism is a file based store but it is also possible to store sessions in memory, in a database or over the network using a protocol known as DRb. These last two options open up the possibility two spray requests across servers and still maintain session state. Rails also makes another associative array available to the controller called flash. Entries in this array only persists from one action to the next making it particularly useful for passing data on redirects.
Often there is functionality that cuts across multiple actions. Although this can obviously be factored out in to a separate method, Rails also has the concept of a filter that can be applied before or after a method. So for example, in my entries application, every action expects to receive the identifier for an event and sets up an attribute in the controller containing the model object for that event as above. The obvious exception to this is the action which is used to select an event. In Rails, this can be expressed simply in the controller class as follows:
1 2 |
before_filter :setup_event, :except => :select_event |
where setup_event is the controller method that does the necessary work and the select_event method is action which does not require this setup to take place. If the functionality cuts across controllers then it is also possible to specify the name of an external class whose filter method will be used.
Lastly, Rails provides a mechanism for performing validation above and beyond the regular expressions in the routing configuration. For, example, the following snippet ensures that, unless the login action is being performed, a user identifier has already been set in to the session state. If it hasn’t, then a redirect takes place having added a message to the flash.
1 2 3 4 |
verify :except => :login, :session => :user_id, :add_flash => { :msg => 'Please log in first' }, :redirect_to => :login |
In the next post in this series, we’ll take a look at how responses are rendered.