4.6. Forms¶
So far we’ve looked at views that display data to the user, but not views that collect data
from the user. The simplest mechanism for doing so consist of HTML forms. To create them, we
address the following steps:
1. How do we display a fill-in form to the user?
2. How is the information filled in by the user made available to the controller action, so that it can be used in a create
or update
ActiveRecord call?
The first step is straightforward: As Section 4.4 described, Rails by default defines a new
action (and route) for this purpose.
Following the pattern seen so far and illustrated in Figure 4.4:
The route
GET /movies/new
names the action, and the route helpernew_movie_path
will generate the URI portion of the route;The action will be handled by the method
MoviesController#new
;By default, the controller action will end by rendering a view
app/views/movies/new.html.erb
.
It seems logical to assume that that view should contain an HTML form that submits to the create
RESTful route. When that HTML form is submitted,
a controller action will need to parse the form data and do something with it—in our case, use it to populate the attributes of a new Movie
object.
To do so, the controller action must have knowledge about how the HTML form fields are named, so that it can extract data from each field and use
that data to populate attributes of an instance of the Movie
model. Such a scenario — a form whose fields represent attributes of an ActiveRecord
model object—is so common that Rails streamlines the process by using form tag helper methods. These helpers are Ruby methods that generate HTML
form tags whose names follow particular conventions that make them easy to parse by the controller action. Figure 4.7 shows an example; watch Screencast
4.6.1 for a description of what’s going on in it.
Specifically, the form_tag helper takes two arguments. The first is the URI to which the form should submit; in this case, the RESTful route helper
(Figure 4.4) is used to generate that URI. The second argument is a hash of optional arguments, one of which may be the HTTP method that should be used
to submit the form. POST is the default so we didn’t need to specify it here; GET is acceptable if submitting the form doesn’t change any application data.
If you specify PUT
, PATCH
, or DELETE
, as Section 4.4 described, the browser will still use POST to submit the form but Rails will “automagically” make it
appear that the form was submitted using the method you specified.
Not all input field types are supported by the form tag helpers (in this case, the date fields aren’t supported), and in some cases you need to generate forms whose fields don’t necessarily correspond to the attributes of some ActiveRecord object. The Rails guide on form tag helpers and Rails API documentation describe the various form tag helper options in detail.
Note that just as with the RESTful route helpers (Figure 4.4), you are not required to use form tag helpers, but as we’ll see next, given the common flow of submitting a form related to a model and using the form’s information to create or update a model instance, using them actually saves you work and results in less code in your views.
To recap where we are, we created the new controller method that will render a view giving the user a form to fill in, placed that view in new.html.erb
, and arranged to
have the form submitted to the create
controller method. All that remains is to have the create controller action parse the form information and use it to create a new
movie in the database.
Recall from the examples in Section 4.2 that the Movie.create!
call takes a hash of attribute names and values to create a new object. The reason to use form tag helpers now
becomes clear: if you look at the HTML page generated by the new.html.erb
template, you’ll see that the form field names created by the form tag helpers have names of the
form params[’movie’][’title’]
, params[’movie’][’rating’]
, and so on. As a result, the value of params[’movie’]
is a hash of movie attribute names and values, which we can pass
along directly using Movie.create!(params[’movie’])
.
It is worth reading the above paragraph again: the form tag helpers give names to the form fields that result in params[’movie’]
containing exactly what needs to be passed to
ActiveRecord’s create
or update_attributes
methods. This streamlining is not only a great example of convention over configuration, but also an example of how a framework can
simplify the “mechanical work” of making the models, views, and controllers in a SaaS app work smoothly together.
We must, however, attend to an important detail before our controller action will work. “Mass assignment” of a whole set of attributes, as occurs when we pass the hash params[’movie’]
to
an ActiveRecord call, is a mechanism that could be used by a malicious attacker to set model attributes that shouldn’t be changeable by regular users. As Figure 4.8 shows, Rails requires
us to declare in the controller which elements of params
are required to be present (if any) for a given action, and critically, which elements are permitted to be assigned to model
attributes. This mechanism follows the principle of least privilege in computer security, a topic to which we return in Section 12.9 when discussing how to defend customer data.
The screencast shows how mass-assignment works in practice, and also shows the helpful technique of using debug breakpoints to provide a detailed look “under the hood” during execution of a controller action.
class MoviesController < ApplicationController
def create
params.require(:movie)
params[:movie].permit(:title,:rating,:release_date)
# shortcut: params.require(:movie).permit(:title,:rating,:release_date)
# rest of code...
end
end
At this point, the controller action has used params[’movies’]
to create a new movie record, ostensibly successfully. But what should we display when the create
action completes?
Strict convention over configuration suggests a view app/views/movies/create.html.erb
that simply confirms the movie was created, and provides a link or other mechanism to go back
to the list of movies, but it seems clumsy to have a separate view just to do that. One alternative would be to streamline the user experience by just sending the user back to the
newly-updated list of all movies (Index action), but arrange to display a confir- mation message displayed somewhere on the page that the movie was added successfully.
Since we already have an action and view to handle Index, the DRY way to proceed is to have the controller action issue an HTTP redirect, telling the Web browser to start an
entirely new request for the Index action. But this approach presents a small problem. Since HTTP is stateless, when this new Index request is routed by Rails to our controller’s
index
action, all of the variables associated with the prior create
request are gone. That is a problem if we want to display a friendly message at the top of the movie list,
since at the moment of the index call, we no longer know the name or ID of the previously-created movie.
To address this common scenario, the flash[]
is a special object available in a controller that quacks like a hash, but whose contents persist from the current request to the next.
In other words, if we put something into flash[]
during the current controller action, we can access it during the very next action, but not during any subsequent actions. The entire
hash is persisted, but by convention, flash[:notice]
is used for informational messages and flash[:alert]
is used for messages about things going wrong. Using the flash in conjunction
with a redirect is so common that the Rails redirect_to
method provides a special syntax for it, as Figure 4.9 shows. In fact, the flash is just a special case of the more general
session[]
, whose contents persist “forever” across requests from the same browser (until you clear it out manually).
class MoviesController < ApplicationController
# 'index' and 'show' methods from Section 4.4 omitted for clarity
def new
@movie = Movie.new
end
def create
if (@movie = Movie.create(movie_params))
redirect_to movies_path, :notice => "#{@movie.title} created."
else
flash[:alert] = "Movie #{@movie.title} could not be created: " +
@movie.errors.full_messages.join(",")
render 'new'
end
end
def edit
@movie = Movie.find params[:id]
end
def update
@movie = Movie.find params[:id]
if (@movie.update_attributes(movie_params))
redirect_to movie_path(@movie), :notice => "#{@movie.title} updated."
else
flash[:alert] = "#{@movie.title} could not be updated: " +
@movie.errors.full_messages.join(",")
render 'edit'
end
end
def destroy
@movie = Movie.find(params[:id])
@movie.destroy
redirect_to movies_path, :notice => "#{@movie.title} deleted."
end
private
def movie_params
params.require(:movie)
params[:movie].permit(:title,:rating,:release_date)
end
end
But which view(s) should attempt to display the contents of the flash? In this example, we chose to redirect the user to the movies listing, so perhaps we should add code to the Index view to display the message. But in the future we might decide to redirect the user someplace else instead, and in any case, the idea of displaying a confirmation message or warning message is so common that it makes sense to factor it out rather than putting it into one specific view.
Recall that app/views/layouts/application.html.erb
is the template used to “wrap” all views by default. This is a good candidate for
displaying flash messages since any pending messages will be displayed no matter what view is rendered, as lines 11–15 of Figure 4.6 show.
Self-Check 4.6.1. Why does the form for creating a new movie submit to the create
method rather than
the new method?
As we saw in Chapter 3, creating a new record requires two interactions. The first one,
new
, loads the form. The second one,create
, submits the form and causes the actual creation of the new record.
Self-Check 4.6.2. Why must every controller action either render a view or perform a redirect?
HTTP is a request-reply protocol, so every action must generate a reply. One kind of reply is a view (Web page) but another kind is a redirect, which instructs the browser to issue a new request to a different URI.
Self-Check 4.6.3. Why does it make no sense to have both a render and a redirect (or two renders, or two redirects) along the same code path in a controller action?
Each request needs exactly one reply. Render and redirect are two different ways to reply to a request.
Self-Check 4.6.4. In line 3 of Figure 4.7, what would be the effect of changing :method=>:post
to :method=>:get
and why?
The form submission would result in listing all movies rather than creating a new movie. The reason is that a route requires both a URI and a method: The
movies_path
helper with theGET
method would route to theindex
action, whereas themovies_path
helper with thePOST
method routes to thecreate
action.
Self-Check 4.6.5. Given that submitting the form shown in Figure 4.7 will create a new movie, why is the view called new.html.erb
rather than create.html.erb
?
A RESTful route and its view should name the resource being requested. In this case, the resource requested when the user loads this form is the form itself, that is,
the ability to create a new movie; hence new
is an appropriate name for this resource. The resource requested when the user submits the form, named by the route specified
for form submission on line 3 of the figure, is the actual creation of the new movie.