Forms
Most forms won't be that long or complicated for you, but it's useful to appreciate all the things you can (and one day will) do with them.
Introduction
You should be familiar with forms, both as a normal Internet user and as an HTML coder who has done the Foundations course. But how much do you REALLY know about forms? It may sound strange, but forms are possibly the most complicated thing about learning web development. Not necessarily because the code itself is difficult, but because you usually want to build forms that accomplish so many different things at once.
Up until now, we've been thinking about Models in Rails on sort of a one-off basis. The User model. The Post model. Sometimes we've had the models relate to each other via associations, like that a Post can has_many
Comment objects. Usually, though, we tend to silo our thoughts to only deal with one at a time.
Now think about a web form to buy an airline ticket. You probably need to enter your name, address, phone number, email, the airline, the flight number, the flight date, your credit card number, your credit card security number, your card expiration date, your card's zipcode, and a bunch of checkboxes for additional things like trip insurance. That's a whole lot of different models embedded in one form! But you still submit it with a single button. Holy macaroni!
Most forms won't be that long or complicated for you, but it's useful to appreciate all the things you can (and one day will) do with them. It's incredibly easy to make a basic form so the first thing we'll do is make sure you've got an intimate understanding of how forms are created in HTML and then how Rails offers you some helpers to make your life easier. We'll cover the way data is structured and sent to the controller until you feel pretty comfortable with that. Then a later lesson will deal with how to take that basic understanding and make forms handle some more firepower.
Learning outcomes
Look through these now and then use them to test yourself after doing the assignment:
How can you view what was submitted by a form?
What is a CSRF Token and why is it necessary?
How do you generate the token in Rails?
Why is the
name
attribute of a form input element so important?How can you nest attributes under a single hash in
params
?Why is this useful?
What do you have to add/modify in your controller to handle nested
params
?What special tags does Rails'
#form_with
helper give you?What is the difference between
#form_tag
,#form_for
and#form_with
helpers?How do you access errors on a failed-to-save model object?
Which form helper automatically adds markup around errors?
How do you access your Update or Delete actions with a form?
Forms in HTML
Step one is to be able to create a form in HTML. Remember how that looks?
There are plenty of input
tags to choose from, including button
, checkbox
, date
, hidden
, password
, radio
and many more (see the full list from W3 Schools).
Viewing what your form submits
If you want to see what your forms are submitting to your Rails app, look through the output that gets printed into your console when you run your $ rails server
. Whenever you submit a very basic form for a user email signup, it should include lines that look something like:
Note: this is from a form that might be generated by a Rails helper method, as explained in a later section below
The first line tells us which HTTP method was used and which route the form went to. The second line tells us which controller and action the form will be handled by. The third line contains everything that will get stuffed into the params
hash for the controller to use. We'll talk about the contents in the next sections.
You'll find yourself looking at this server output a lot when you start building forms. It'll keep you sane because it tells you exactly what the browser sent back to your application so you can see if there's been a... misunderstanding.
Railsifying your form
The first thing you'll realize if you try to create a plain vanilla form in a Rails view is that it won't work. You'll either get an error or your user session will get zeroed out (depending on your Rails version). That's because Rails by default automatically protects you from cross-site request forgery and it requires you to verify that the form was actually submitted from a page you generated. In order to do so, it generates an "authenticity token" which looks like gibberish but helps Rails match the form with your session and the application.
You'll notice the token in the server output from above:
So, if you want to create your own form that gets handled by Rails, you need to provide the token somehow as well. Luckily, Rails gives you a method called form_authenticity_token
to do so, and we'll cover it in the project.
Making forms into params
What about the other form inputs, the ones we actually care about?
Each one of these inputs is structured slightly differently, but there are some commonalities. One important thing to note is the name
attribute that you can give to an input tag. In Rails, that's very important. The name
attribute tells Rails what it should call the stuff you entered in that input field when it creates the params
hash. For instance,
Will result in your params
hash containing a key called description
that you can access as normal, e.g. params[:description]
, inside your controller. That's also why some inputs like radio buttons (where type="radio"
) use the name
attribute to know which radio buttons should be grouped together such that clicking one of them will unclick the others. The name
attribute is surprisingly important!
Now another thing we talked about in the controller section was nesting data. You'll often want to tuck submitted data neatly into a hash instead of keeping them all at the top level. This can be useful because, as we saw with controllers, it lets you do a one-line #create
(once you've whitelisted the parameters with #require
and #permit
). When you access params[:user]
, it's actually a hash containing all the user's attributes, for instance {first_name: "foo", last_name: "bar", email: "foo@bar.com"}
. How do you get your forms to submit parameters like this? It's easy!
It all comes back to the name
attribute of your form inputs. Just use hard brackets to nest data like so:
Those inputs will now get transformed into a nested hash under the :user
key. The server output becomes:
Specific parameters of the params
hash are accessed like any other nested hash params[:user][:email]
.
Don't forget that you have to whitelist the params now in your controller using require
and permit
because they are a hash instead of just a flat string. See the Controller section below for a refresher on the controller side of things.
This is cool stuff that you'll get a chance to play with in the project.
Form helpers: form_with
Rails tries to make your life as easy as it can, so naturally it provides you with helper methods that automate some of the repetitive parts of creating forms. That doesn't mean you don't need to know how to create forms the "old fashioned" way... it's actually MORE important to know your form fundamentals when using helpers because you'll need to really understand what's going on behind the scenes if something breaks.
Start by making a form using the form_with
helper, which takes a block representing all the inputs to the form. It takes care of the CSRF security token we talked about above by automatically creating the hidden input for it so you don't have to. You pass it arguments to tell it which path to submit to (the default is the current page) and which method to use. Then there are tag helpers that create the specified tags for you, like text_field_tag
below. All you need to specify there is what you want to call the field when it is submitted.
Creates the form:
There are tag helpers for all the major tags and the options they accept are all a bit different. See the reading assignment for more detail.
There are a few things to take note of when using the form_with
helper.
The ID of the inputs matches the name.
The second line ends with
as JS
instead of the usualas HTML
when you look at your output in your console after submitting a form. By default, all forms using form_with will submit data using an XHR (Ajax) request. This means that a full request cycle doesn't occur and the page doesn't reload when the form is submitted. In order to disable this, just includelocal: true
when building your form like this.
You can also check your Network tab in your browser to see the requests in both cases.
Using models with the form_with helper
More often than not, you'll want your form to act on the attributes of an existing model. Like specifying a title (or whatever other fields are required for your model) of a new news Article.
Just pass form_with
a model object, and it will make the form submit to the URL for that object, e.g. @article
will submit to the correct URL for creating an Article. Remember from the lesson on controllers that the #new
action usually involves creating a new (unsaved) instance of your object and passing it to the view... now you finally get to see why by using that object in your #form_with
forms!
From the Rails Guide:
This will produce the following HTML:
The best part about form_with
is that if you just pass it a model object like @article
in the example above, Rails will check for you if the object has been saved yet. If it's a new object, it will send the form to your #create
action. If the object has been saved before, so we know that we're editing an existing object, it will send the object to your #update
action instead. This is done by automatically generating the correct URL when the form is created. Magic!
Deprecated form helpers: form_tag and form_for
Before form_with
was introduced in Rails 5.1, form_tag
and form_for
were used. Both are now soft-deprecated but are likely to be seen in existing codebases. The difference between them is that form_for takes a model whereas form_tag takes a URL (used when you need a form but don't have an underlying model). The form_with helper can be used with either a model form_with(model: @user)
or a URL form_with(url: users_path)
.
You can read about the form_tag
and form_for
helpers in an older version of the Rails Guides.
Forms and validations
What happens if your form is submitted but fails the validations you've placed on it? For instance, what if the user's password is too short? Well, first of all, you should have had some JavaScript validations to be your first line of defense and they should have caught that... but we'll get into that in another course. In any case, hopefully your controller is set up to re-render the current form.
You'll probably want to display the errors so the user knows what went wrong. Recall that when Rails tries to validate an object and fails, it attaches a new set of fields to the object called errors
. You can see those errors by accessing your_object_name.errors
. Those errors have a couple of handy helpers you can use to display them nicely in the browser -- #count
and #full_messages
. See the code below:
That will give the user a message telling him/her how many errors there are and then a message for each error.
The best part about Rails form helpers... they handle errors automatically too! If a form is rendered for a specific model object, like using form_with model: @article
from the example above, Rails will check for errors and, if it finds any, it will automatically wrap a special <div>
element around that field with the class field_with_errors
so you can write whatever CSS you want to make it stand out. Cool!
Making PATCH and DELETE submissions
Forms aren't really designed to natively delete objects because browsers only support GET and POST requests. Rails gives you a way around that by sticking a hidden field named "_method" into your form. It tells Rails that you actually want to do either a PATCH (aka PUT) or DELETE request (whichever you specified), and might look like <input name="_method" type="hidden" value="patch">
.
You get Rails to add this to your form by passing an option to form_with
called :method
, e.g.:
Controller refresher
Just as a refresher, here's a very basic controller setup for handling #new
actions and #create
actions.
We wanted to show this again so you could remind yourself what's going on in the form's lifecycle. The user presumably went to the path for the #new
action, likely http://www.yourapp.com/users/new
. That ran the #new
action, which created a new user object in memory (but not yet saved to the database) and rendered the new.html.erb
view. The view probably used form_for
on @user
to make things nice and easy for the developer.
Once the form gets submitted, the #create
action will build another new User object with the parameters we explicitly tell it are okay. Recall that our custom #user_params
method will return the params[:user]
hash for us, which lets User.new
build us a complete new instance. If that instance can be saved to the database, we're all good and we go to that user's show.html.erb
page.
If the @user
cannot be saved, like because the first_name
contains numbers, we will jump straight back to rendering the new.html.erb
view, this time using the @user
instance that will still have errors attached to it. Our form should gracefully handle those errors by telling the user where they screwed up.
Assignment
Read the Rails Guide on Form Helpers, sections 1 to 3.2.
Skim 3.3 to 7 to see what kinds of things are out there. One day you'll need them, and now you know where to look.
Read sections 8.1 and 8.2 for the official explanation of how parameters are created from the
name
attribute.Read the Rails Guide on Validations section 8 for a quick look at displaying errors.
Skim through the official Rails API section on the form_with helper to see various ways to use this helper tag.
Conclusion
At this point, you should have a solid understanding of how forms work in general and a pretty good feel for how Rails helps you out by generating them for you. You'll get a chance to build a whole bunch of forms in the next few projects, so don't worry if it's not totally stuck for you yet. Seeing it in action will make things click.
Additional resources
This section contains helpful links to other content. It isn't required, so consider it supplemental for if you need to dive deeper into something.
Last updated