Rails forms for Beginners

Published: 2014-11-13

Forms are second only to normal views for how your users interact with your application. Designing easy to use forms that make it easy for your users to enter their data into your app can make a big difference in how 'hard' your users view your application.

Here I will go over creating forms for your models in Rails. In a future article I will cover using a gem called formtastic to help speed up form creation.

Prerequisites

You will need a basic understanding of model validations and Active record associations which has been covered in previous articles.

The Setup

All the examples in the article will be built and tested using the following versions:
  • Rails: 4.1.7
  • Ruby 2.1.4
  • MiniTest 5.4.3

To help with the testing side of things, you can read my article on Rails Automated Testing Setup for Beginners to help you get setup with automated testing if you haven't already.

The data models

So we can have a lot of examples, lets build a classic Blog post model and add enough fields so we can have an example of each type of input:

  • Title - standard input
  • Body - Text input
  • Category - Select list
  • Status - Radio Buttons
  • Tags - Check Boxes

Now normally you would either have a category (belongs_to association) or tag (has_many association). For our demonstration purposes we have both.

Generate our models

First we generate our models so Rails will create all our files.

# use a scaffold for post as this will generate our initial form view.
rails generate scaffold Post title:string body:text category_id:integer status_id:integer
rails generate model Category name:string
rails generate model Status name:string
rails generate model Tag name:string
rails generate model Tagging post_id:integer tag_id:integer
Don't forget to migrate the database with: rake db:migrate

Now add relationships to the models

class Post < ActiveRecord::Base
  belongs_to :category
  belongs_to :status
  has_many :taggings
  has_many :tags, through: :taggings # notice the use of the plural model name
end
class Category < ActiveRecord::Base
  has_many :posts
end
class Status < ActiveRecord::Base
  has_many :posts
end
class Tagging < ActiveRecord::Base
  belongs_to :post  # foreign key - post_id
  belongs_to :tag   # foreign key - tag_id
end
class Tag < ActiveRecord::Base
  has_many :taggings
  has_many :posts, through: :taggings # notice the use of the plural model name
end

Next some seed data to get started

These commands need to be run in a rails console

# create categories
Category.create(name: 'Technology')
Category.create(name: 'Lifestyle')
Category.create(name: 'Business')
Category.create(name: 'Entertainment')
# create status
Status.create(name: 'just created')
Status.create(name: 'review')
Status.create(name: 'published')
# create tags
Tag.create(name: 'Fun')
Tag.create(name: 'Scary')
Tag.create(name: 'Sad')
Tag.create(name: 'Excited')

Creating a Standard form

Thanks to the Rails scaffold command, you already have a form for the Post model.

app/views/posts/new.html.erb will look like:

<h1>New post</h1>

<%= render 'form' %>

<%= link_to 'Back', posts_path %>

It renders the form separately so that both the new and edit views can use the same form.

app/views/posts/_form.html.erb will look like:

<%= form_for(@post) do |f| %>
  <% if @post.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:
      </h2>

      <ul>
      <% @post.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </div>
  <div class="field">
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </div>
  <div class="field">
    <%= f.label :category_id %><br>
    <%= f.number_field :category_id %>
  </div>
  <div class="field">
    <%= f.label :status_id %><br>
    <%= f.number_field :status_id %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Lets break it down into the core parts and go through one by one.

Form For

<%= form_for(@post) do |f| %>
  <!-- ... the form objects -->
<% end %>

The form_for(@post) tags tell rails that this form is for a @post object. This will tell rails what action to use when the submit button is pressed. You can pass a few different options, though for now just leave it as is and it will work.

Error Messages

  <% if @post.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:
      </h2>
      <ul>
      <% @post.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

Next is the error message code, if you have any validations in your model and the form fails to save, then Rails will populate the .errors method and this will display them to the user.

Once again, Rails has a lot of sensible defaults and its best to leave this alone for now.

Text Field

The most common field for a form, matches up to a string column. This is your standard single row input.

  <%= f.label :title %><br>
  <%= f.text_field :title %>
### Generated output ###
  <label for="post_title">Title</label><br>
  <input id="post_title" name="post[title]" type="text" />
Notice that the input ID is generated by model_column. This gives you a unique ID to use for javascript and css.

Common customizations are to set the size of the field and css class:

  <%= f.label :title %><br>
  <%= f.text_field :title, size: 80, class: 'input' %>
### Generated output ###
  <label for="post_title">Title</label><br>
  <input type="text" size="80" name="post[title]" id="post_title" class="input"> 

Text Area

Next up is text area which matches up to text fields in your model.

  <%= f.label :body %><br>
  <%= f.text_area :body %>
### Generated output ###
  <label for="post_body">Body</label><br>
  <textarea name="post[body]" id="post_body"></textarea>

Common customizations are to set the columns and rows of the area and css class:

  <%= f.label :body %><br>
  <%= f.text_area :body, cols: 80, rows: 5, class: 'input' %>
### Generated output ###
  <label for="post_body">Body</label><br>
  <textarea rows="5" name="post[body]" id="post_body" cols="80" class="input"></textarea> 

Select List

When you have a relationship with another model, it is easiest to present this as a select list. The Rails scaffold will select a number_field, though you can easily replace that with a select field.

  <%= f.label :category_id %><br>
  <%= f.select :category_id, 
    options_from_collection_for_select(Category.all, :id, :name, @post.category_id) %>
### Generated output ###
  <label for="post_category_id">Category</label><br>
  <select id="post_category_id" name="post[category_id]">
    <option value="1">Technology</option>
    <option value="2">Lifestyle</option>
    <option value="3">Business</option>
    <option value="4">Entertainment</option>
  </select> 

All the heavy lifting is done by the options_from_collection_for_select helper.
It takes 4 arguments:

  • collection - this is the collection of data that you want the user to choose from. Generally best to generate it in the controller and reference it here.
  • value_method - the method to use on a member of the collection to get the value for the select list. Generally :id
  • text_method - the method to use on a member of the collection to get the display text for the select list.
  • selected = nil - this will set a value to 'checked', needed for when the form is being edited or re displayed on error so that it selects the already selected category, needs to match the value. In our case it is @post.category_id

Rails does all the heavy lifting with just a single line. Make sure you have the 4th value or you won't get the correct item selected when displaying the form. It defaults to nil if empty and will cause your users issues.

Radio Buttons

Radio buttons are done similarly in that a helper collection_radio_buttons will do all the heavy lifting.

  <%= f.label :status_id %><br>
  <%= collection_radio_buttons(:post, :status_id, Status.all, :id, :name) %>
### Generated output ###
  <label for="post_status_id">Status</label><br>
  <input id="post_status_id_1" name="post[status_id]" value="1" type="radio">
  <label for="post_status_id_1">just created</label>
  <input id="post_status_id_2" name="post[status_id]" value="2" type="radio">
  <label for="post_status_id_2">review</label>
  <input id="post_status_id_3" name="post[status_id]" value="3" type="radio">
  <label for="post_status_id_3">published</label>

It takes 5 arguments with optional options and html_options

  • object - this is the object that we are referencing, in our case @post.
  • method - the method to use on the object to get the current selection, needed when doing an edit or re displaying the form.
  • collection - this is the collection of data that you want the user to choose from. Generally best to generate it in the controller and reference it here.
  • value_method - the method to use on a member of the collection to get the value for the select list. Generally :id
  • text_method - the method to use on a member of the collection to get the display text for the select list.

Once again Rails does it all with a single line.

Check Boxes

For our example we need to manually add the tags to the form as it is a many to many relationship. Once again a Rails helper will do the heavy lifting for us.
This time though, we need to modify the strong paramters in the controller to allow the tags.

    <%= f.label :tags %><br>
    <%= collection_check_boxes(:post, :tag_ids, Tag.all, :id, :name) %>
### Generated output ###
  <label for="post_tags">Tags</label><br>
  <input id="post_tag_ids_1" name="post[tag_ids][]" value="1" type="checkbox">
  <label for="post_tag_ids_1">Fun</label>
  <input id="post_tag_ids_2" name="post[tag_ids][]" value="2" type="checkbox">
  <label for="post_tag_ids_2">Scary</label>
  <input id="post_tag_ids_3" name="post[tag_ids][]" value="3" type="checkbox">
  <label for="post_tag_ids_3">Sad</label>
  <input id="post_tag_ids_4" name="post[tag_ids][]" value="4" type="checkbox">
  <label for="post_tag_ids_4">Excited</label>
  <input name="post[tag_ids][]" value="" type="hidden">

The collection_check_boxes helper works exactly the same as the previous collection_radio_buttons helper. This time though, the helper is a plural due to the many to many relationship.

Now the controllers strong parameters need to be modified to allow the tags through as they weren't put there by the Rails scaffold generator.

Before:

def post_params
  params.require(:post).permit(:title, :body, :category_id, :status_id)
end

After:

def post_params
  params.require(:post).permit(:title, :body, :category_id, :status_id, { :tag_ids => [] })
end

The addition of { :tag_ids => [] } tells the controller to permit an array of tag_ids through. Rails will then do the rest.

Now you can do fancy forms

Now you know how to do all the common types of forms that your users will require to input their data.

My rule of thumb: Try not to ask your users to fill out complicated forms, keep it as simple as possible and only collect the minimum that you need. Use good defaults if at all possible.

Resources

RailsGuides for Form Helpers - Covers using forms with and without a model in Rails.

Form Helpers API Doc - More details and examples on form helpers.

Form Options Helpers API Doc

Rails Automated Testing Setup for Beginners - My article on how to setup MiniTest for easy automated testing.


comments powered by Disqus