Rails Form Processing
In previous episodes, we’ve described how the model, view, and controller work together to create web pages. Now we’re going to explore a more complicated example: displaying a form that the user completes, validating the submitted data, displaying error messages if there’s a validation failure, and storing the information in the database if it passes validation.
As in past episodes, we’re going to focus on the concepts and avoid actual code. Once you understand the concepts, it’s a lot easier to learn the coding specifics.
Whatever information you’re storing in a web application’s database, whether it is products, transactions, podcasts, books, or contact information, you’re probably going to need to do four basic things with it, which go by the lovely acronym of CRUD: create, read, update, and destroy.
You may more naturally think of these operations as making a new database record, using that information, editing the record, and deleting it. That’s CRUD, said a little differently.
If a user is entering information in a web application, it’s probably in an HTML form. So forms play a big part in most Rails applications. To facilitate this nearly universal need, Rails provides an abundance of helpers that make form coding and processing about as simple as it can be.
Writing Form Views
You could write your own HTML form code and put it in a Rails view file, but Rails provides a much simpler alternative: a rich set of form helpers that enable you to create virtually the entire form in Ruby code, with Rails worrying about creating appropriate HTML.
At the start of a form, you use a helper called form_for, and then you provide the name of the object whose values are being set by the form. Using just the name of this object, Rails will figure out what controller to use (by appending “controller” to the pluralized name of the object).
So “form_for podcast” creates the start of a form for entering the podcast information. The submit URL will automatically be directed to a controller named podcasts_controller, specifying either the “create” action, if it is a new record, or the “update” action, if modifying an existing record.
So suppose you have a database of podcast episodes. The form starts with:
(The @ symbol before the name indicates that this is an instance variable, which is created by the controller for the view’s use.)
Then you create the fields that you want to display, using helpers with names like text_field, text_area, radio_button, select, and so forth. For each field, you specify one attribute of the model (which, you’ll remember, was specified in the form_for tag). So you might define a text_field for the podcast title, and a text_area field for the podcast description.
You can create your entire form without writing a bit of HTML code. In the real world, you’d typically want to wrap each form elements in some HTML element, perhaps a paragraph, or a div, or a table cell, so your view file will have simple HTML tags wrapping the form field helpers.
More complex forms
As long as your form consists of all or some of the fields for a single model, the code is supremely simple. If you need to draw from multiple models, such as getting a person’s address from one model while the person’s name comes from another, it’s only slightly more complex; there’s a helper called fields_for, which allows you to specify fields that come from any model.
Suppose we have another model called Link and the links table stores links associated with a particular podcast. After the form_for block, which starts the form for the podcast, you might have some podcast fields, and then you’d have a
fields_for links helper. Within the fields_for block, you specify the fields that are associated with that model.
Forms get somewhat more complex when you have multiple instances of one model in a single form. For example, a customer profile form might allow multiple addresses to be entered, each of which creates an instance of the address model.
When you display the form, you want to list each of those instances, of which there may be a variable number, and allow them to be edited.
The specifics of how to do this are beyond what we can cover in this podcast, but suffice it to say that it’s a bit more complex than a basic form, but not fundamentally difficult.
Another common behavior in a form is to have certain fields come and go depending on the state of other fields. For example, you may want the shipping address fields to appear only if a check box labeled “use shipping address” is selected. We discussed how this sort of thing works back in episode 3.
What happens when a form is submitted
When the user clicks submit on a form, Rails invokes either the create (for a new record) or update (for an existing record) action in the associated controller. Forms are, by default, submitted with an HTTP POST command, and all the information that was entered in the form is provided in a hash called params, which is available to the controller.
A hash, as you may remember from episode 2, is a set of key-value pairs. Think of it as a set of named items, with the keys being the names. The value can be any Ruby object; it could be another hash, for example, which turns out to be how form data is passed.
The params hash includes, for each model used in a form, a hash whose name is the name of the model. For example, if the model is podcast, there will be a hash named podcast in the params hash. Within this podcast hash are key-value pairs for each of the fields in the form.
For simple forms, you don’t actually need to understand any of this. Rails is all set up to create and update objects based on the information in the params hash, so all you have to do is point Rails to the hash and tell it (using a few lines of Ruby in the controller) to save or update the corresponding record.
If your form has data from multiple models, the params hash will include multiple hashes, one for each model. If there are multiple instances of a single model, there will be an array of hashes for that model. Depending on the situation, you may need a little Ruby code in the controller to process data from multiple models.
It is possible to submit form data using an HTTP GET instead of POST. You can specify this as an option in the form_for helper that begins the form. If you do so, then all of the parameters appear in the URL when the form is submitted, which is generally undesirable.
Submitting a form with a GET operation is typically used for search forms. By using a GET to submit the form, the user can bookmark the results page, and going back to that bookmark will resubmit the search query, since the URL contains the entire search specification as query-string parameters (for example, mysite.com/search?subject=“Ruby on Rails”).
Processing form data in the controller
The create action, by convention, deals with creating new records from form submissions. It doesn’t have to do much; at its most basic, the create action needs only one line of code. Continuing with our podcast example, we could write:
Invoking the create method on the podcast model creates a new podcast object and then saves it to the database. You need only to pass the podcast hash to the create method, since this hash has all the data from the form fields relevant to that model. No matter how many fields you might have in your form, it’s just one simple line of code for each model.
In the real world the controller code for the create action is a little more than just a create statement, primarily because you generally want to validate the data from the user before storing it in the database. For example, we don’t want to store a podcast record if the title is blank.
Where should we validate the data? It could be done in the controller, but the right place to do this is in the model. You want validations to occur there because you want them to apply to any possible way data might be submitted. For example, it might come in via a web service call, as well as through a form.
As we discussed in episode 4, there’s a rich set of validation helpers, and you can write your own validations as well. In the simple case of wanting to ensure that our new podcast has a title, we’d write in the podcast model:
That’s simple enough, but what happens when it fails? The create method will return false in this case, signaling to the controller that the data was not saved. In this case, the controller should cause the form to be displayed again, with validation error messages, and with the erroneous fields highlighted.
Rails does all of this almost magically. In the controller, you need code that essentially says:
- If the record was successfully created, then continue (in which case you might want to go to the list of podcasts, for example, or to a page that shows the current podcast).
- If the record was not successfully created (in which case, the podcast.create method returned false), then redisplay the form.
This actually takes many fewer words in actual Ruby code:
if Podcast.create params[:podcast] redirect_to (wherever you want to go after a successful save) else render :action => :new end
Displaying validation errors
If any of the validations fail when attempting to save an object to the database, the model returns an array of error messages. You don’t have to do anything other than to define your specific validations; the ability to generate error messages is part of the rich set of capabilities that your models gain from ActiveRecord.
In your view, you need to do two things: display the validation errors, and highlight the fields in which those errors occurred. I hope it is not a surprise by now that Rails provides helpers that make this trivial.
To display the error messages, all it takes is a call to the error_messages_for helper, specifying the name of the object for which you want to display the message. So our podcast form would have a line that reads:
This helper creates the HTML for a box that shows the messages. Rails provides default CSS, which you can of course modify, to put the messages in a red box.
To highlight the affected fields, Rails automatically wraps the fields for which there are errors in a div with a CSS class applied. The default Rails CSS applies a red background to such fields.
You can provide complete error message text as part of your validation code, if you’d like. If you don’t, Rails automatically creates a message based on the name of the attribute and the type of validation used.
For example, our podcast model has a validation that says the title cannot be blank, so Rails will create an error message that says just that: “title cannot be blank”.
If the fields in the form draw from multiple, different models, you can simply include each of their names when you invoke the “error_messages_for” helper. It’s a little more complicated if you have multiple instances of a single model in a form; it takes a few lines of Ruby in the controller to collect the error messages in a reasonable way.
With a standard form submission, you’re navigating to a new page. If you want to redisplay the page the form was on, such as when adding a comment to a blog post, the user has to wait while all the information that is already on their screen is fetched from the server again.
You can streamline this experience using Ajax forms. With an Ajax form, submitting the form does not trigger a new page. Instead, an XML HTTP request is sent to the server. The form data is sent as POST data, just as for a normal submit, but the page that the user was on remains unchanged, at least for the moment.
The XML HTTP request is directed to a controller action much like the create or update action that processes a standard form submission. After saving (or attempting to save) the object, the controller invokes a view that returns just a snippet of HTML, rather than an entire page, to the browser.
What does the browser do with this bit of HTML? It updates a DOM element (that is, a piece of the page), whose ID was specified in the XML HTTP request. For example, you might include an empty div, with the id of “new_comment”, in the form. When the result from the XML HTTP request comes back, the div is automatically updated to include that HTML.
Returning to our blog comment example, you could use RJS to modify two page elements when a new comment is submitted, deleting the comment form and updating the list of comments displayed to include the new comment.
Implementing Ajax forms
Just as with a regular form submission, you need to write a little bit of Ruby code in the controller, and HTML code in the view, to support handling of validation errors that may occur.
There’s some other related facilities in Rails that we don’t have time to explain fully here, but we’ll mention them so you can investigate further if they’re of interest.
- As an alternative method of Ajaxifying a form, there’s a “submit_to_remote” helper that you can use in place of the normal submit helper. With this approach, you use a normal form_for, rather than remote_form_for.
- There’s a “link_to_remote” helper that you can use anywhere, without needing a form at all, to make an Ajax request.
- You can use the Scriptaculous effects to animate the changes to the page, which can be important for usability.
- You can specify actions to be performed when certain events occur during the processing of a request. For example, you’ll typically want to display a spinner icon to show that the server is working when the request is sent, and then hide it when processing completes.
In this episode, we’ve explored how a diverse assortment of Rails helpers and other facilities make it easy to create and process forms to enter information into your database, and to modify information that is already there.
The form_for helper handles the setup for your form, creating the required HTML and directing the submission to the appropriate controller action. Helpers for each of the various field types create the form fields themselves.
Helpers in the model make validating form data easy, and view helpers take care of displaying messages and highlighting fields that have errors. And by using remote_form_for, you can implement Ajax forms almost as easily as conventional forms.
In our next episode, we’re going to talk about development practices that are part of the Ruby on Rails ecosystem, including source code control, deployment, documentation, and debugging.