Having covered the Model and Controller aspects of Rails in previous posts, that really means it has to be View next. I’ve already touched on the basics: the controller method renders a template, either explicitly or simply by exiting and allowing the default template for the action to rendered, and the attributes of the controller class setup by the action method are then available to the template.
Unlike some web frameworks, Rails doesn’t have a whole new language for templating. The contents of the template is output with inline Ruby expressions contained within < %= %>
tags and Ruby fragments enclosed in < % %>
tags. (A nice touch to aid formatting is that the end tag -%>
ignores the following newline.) Results of inline expressions are output as-is so typically you want to use the html_escape()
method, conveniently aliased as h()
. There are also a whole raft of other formatting helpers and the all important debug()
method that dumps out a HTML formatted version of its parameter.
In general you don’t actually write much HTML in the template with helper functions available for most tags. For example, link_to()
generates a link tag and text either to an absolute URL or to another Rails action (using the routing rules to work out the required URL). It’s well worth looking at the ActionView documentation as there are lots of helpful variants and parameters. For example, link_to_unless_current()
is great for menu trees and the :encode => "javascript"
parameter is great for obscuring e-mail addresses when using mail_to()
.
Forms are where the helpers really come in to their own though. Take the following example where the template has been passed an entry as an attribute.
1 2 3 4 5 6 7 |
< %= start_form_tag %> < % for @competitor in @entry.competitors %> < %= text_field("competitor[]", 'forename') %> < %= text_field("competitor[]", 'surname') %><br /> < % end %> < %= submit_tag %> < %= end_form_tag %> |
This code will create a form containing text fields with the current forename and surname values for all of the competitors on the entry form. When submitted, the controller parameter competitor
will be a hash of hashes. The outer hash is indexed by competitor id and the inner hash by attribute name. This would allow the controller to perform a database update of all competitor names for the entry with the simple command:
1 |
Competitor.update(params[:competitor].keys, params[:competitor].values) |
Where this broke down for me was when trying to display a third dimension on a single form. For example, if every competitor then has a course for each day of a multi-day event. Not an insurmountable problem but it needed more code.
This example could actually be written better using another Rails capability: partial templates. Templates can call out to other templates passing in objects for them to render. Better still, a partial template can be applied to a collection of objects, so the for loop above could be replaced by:
1 |
< %= render(:partial => "competitor", :collection => @entry.competitors) %> |
where the partial template named _competitor.rhtml
would then contain the body of the loop. You can even specify a :spacer_template
parameter on the call to render()
which identifies a template to be invoked between each entry in the collection.
The last concept I’ll cover is the layout. Typically you’ll want to apply some consistent layout across all of the actions associated with a controller (or even the whole application). Confusingly this is what other frameworks might refer to as a template but Rails calls this a layout. By default there is a separate layout for each controller which identifies where the result of the action template should be placed using < %= @content_for_layout %>
. As you would expect, layouts are not applied to partial templates and can also be disabled explicitly, for example when returning a file rather than HTML from a particular action.
Lastly, I should point out that you can automatically generate CRUD actions and corresponding views for a model object. For example:
1 |
ruby script/generate scaffold Competitor Entry |
would create a controller called Entry
with CRUD actions and views for the Competitor
model object. The views are pretty basic but they act as a good starting point for a newbie. In my case, I was trying to create a single entry form page which would populate multiple model objects but I could still cut and paste relevant snippets of code from the scaffold. If you’re happy with the basic CRUD operations but want prettier views (and all without generating any code), then check out ActiveScaffold.