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 helper new_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
Figure 4.8: Rails’ “strong parameters” mechanism requires us to declare which values from params are required to be present and which values are permitted to be used to update the model. require and permit operate on the params hash or any of its sub-hashes, as the Rails documentation8 explains. In Section 5.1 we introduce before-filters, which can be used to DRY out this code rather than repeating it in any controller action that might try to create or modify a model instance.

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
Figure 4.9: Putting together the main ideas of this section, here is a simple controller that handles the CRUD actions for the Movie model. The create and update actions make use of the fact that Rails form helpers arrange to populate params[’movies’] with a hash of attribute values ready to pass to ActiveRecord, but the private movie_params method tells Rails which params are required and which are “safe” to pass along to ActiveRecord.

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 the GET method would route to the index action, whereas the movies_path helper with the POST method routes to the create 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.