Creating Forms in Ruby on Rails with Simple Form
Tags: ruby, rails
Published: 2024-05-29 11:55:19

Ruby on Rails has changed how we build web applications. Early on, the framework came with some great features to help you get started and build robust applications.

However, it can still be tricky to build and handle forms. Simple Form is a great option. Let's examine what Simple Form is, why we might need it, and some real use cases.

## Forms and Ruby on Rails

Ruby on Rails really simplifies building applications. Yet, it also requires your constant attention to keep your codebase streamlined and coherent. One strategy is to avoid writing code by using abstractions, for example. Code that isn't in your actual application is code you don't have to manage. That's why we like abstractions so much, right? Simple Form is just such an abstraction that simplifies building forms for web pages.

Forms are generally complex to build, even using Ruby on Rails. Each field requires attention to define the right type and attach it to the correct attribute. Simple Form eases that pain, as you don't need to find the exact type required for each field.

Let's take a form that gets a user's details, with `name`, `username`, and `email` fields. Consider that we have the `User` model with similar related attributes. With Simple Form, the form that fills in attributes looks like the following:

```erb
<%= simple_form_for @user do |f| %>
  <%= f.input :name, required: false %>
  <%= f.input :username %>
  <%= f.input :email %>
  <%= f.button :submit %>
<% end %>
```

Note that we are not specifying the types of each field. Simple Form will pick the correct input type for each field based on the column's type.

This saves us time and keeps the code readable and easier to maintain.

## Installing Simple Form

To install Simple Form in your project, add the `simple_form` gem to the Gemfile. Once you have run `bundle install`, you can use the included generator to set it up correctly for your application.

```sh
rails generate simple_form:install
```

**A note on Bootstrap and Zurb Foundation:** _Both Bootstrap and Zurb Foundation 5 are supported within Simple Form. So, if you are using one of them, you can use the `--bootstrap` or `--foundation` parameter with the generator to include additional configurations during the installation. In the case of Bootstrap, this would look like the following:_

```sh
rails generate simple_form:install --bootstrap
```

## Basic Usage of Simple Form

As we pointed out earlier, Simple Form will generate a complete form mainly based on the data related to your objects. Yet, it's also important to understand that Simple Form will not only generate the appropriate field for each attribute but also provide labels and display error hints above the input fields themselves!

Let's consider a fresh new Ruby on Rails 7.x application. We'll generate a User model and build a form for it using Simple Form.

```sh
rails g model User username:string password:string email:string remember_me:boolean
# and follow with running the migration of course
rails db:migrate
```

Now we can generate a controller with the `new`, `create`, and `index` actions. The aim is to have something we can play with in the form.

```sh
rails g controller users
```

```ruby
# app/users_controller.rb
class UsersController < ApplicationController
  def new
    render :new, locals: { user: User.new }
  end

  def create
    user = User.create(user_params)
    redirect_to users_path
  end

  def index
    render :index, locals: { users: User.all }
  end

  private

  def user_params
    params.require(:user).permit(:username, :password, :email)
  end
end
```

```ruby
# config/routes.rb
Rails.application.routes.draw do
  resources :users, only: [:new, :create, :index]
end
```

These actions require relevant views. Here is a form that handles a user signing up or being added:

```erb
<!-- app/views/users/new.html.erb -->
<%= simple_form_for user do |f| %>
  <%= f.input :username, label: 'Your username please', error: 'Username is mandatory, please specify one' %>
  <%= f.input :password, hint: 'No special characters.' %>
  <%= f.input :email, placeholder: 'user@domain.com' %>
  <%= f.input :remember_me, inline_label: 'Yes, remember me' %>
  <%= f.button :submit %>
<% end %>
```

In this case, we specify a custom label and error for the `username` input. If no such customization is passed, it will do its best to guess what the label needs to be based on the attribute's name.

We have set a custom hint for the `password` field and a custom `placeholder` for the `email` field. Notice, also, the ability to specify an "inline label" instead of one located above the field (the usual default in forms).

It's also possible to disable labels, hints, and errors by passing the `false` value for those attributes: `<%= f.input :password_confirmation, label: false %>`.

We need to complement this with one more view to make it usable.

```erb
<!-- app/views/users/index.html.erb -->
<ul>
<% users.each do |user| %>
  <li><%= user.username %> (<%= user.email %>)</li>
<% end %>
</ul>
<p>
<%= link_to 'New User', new_user_path %>
</p>
```

You can then start the application using `rails s`. Head to `localhost:3000/users/new` to see and use the form.
Let's focus on how the form differs from a classic Ruby on Rails form.

**A note on validations**: _Simple Form can work with mandatory fields out of the box. For example, if we were to add the following presence validation in our User model:_

```ruby
class User < ApplicationRecord
  validates :username, presence:true

  # more code
end
```

_This will be used by Simple Form to add a little '\*' next to the `username` field, specifying that it's mandatory. This doesn't manage the errors automatically, so you still have to handle that in the controller action. Return to the appropriate field that has errors and act on them._

### About Column Types and Form Fields

As mentioned earlier, we haven't specified a type for each field. The [Simple Form README](https://github.com/heartcombo/simple_form#available-input-types-and-defaults-for-each-column-type) has a complete list of all available input types and the defaults for each column type. Here are the most used ones:

| Column Type          | Generated HTML element | Comment                                      |
| -------------------- | ---------------------- | -------------------------------------------- |
| `string`             | `input[type=text]`     |                                              |
| `boolean`            | `input[type=checkbox]` |                                              |
| (passwords) `string` | `input[type=password]` | Any column with a name containing 'password' |
| `text`               | `input[type=textarea]` |                                              |
| `integer`, `float`   | `input[type=number]`   |                                              |
| `datetime`           | `datetime select`      |                                              |
| `time`               | `time select`          |                                              |
| `country`            | `select`               |                                              |

### Booleans

As we can see in the above table, boolean attributes will be represented, by default, as checkboxes. In many cases, that's what we'll want. But if not, there is a way to customize this in Simple Form with the `as` attribute, which allows you to specify if you will show radio buttons or a dropdown instead.

Here is how to specify radio buttons instead:

```erb
<%= f.input :remember_me, input_html: { value: '1' }, as: :radio_buttons %>
```

This will generate the following HTML:

```html
<div class="input radio_buttons optional user_remember_me">
  <label class="radio_buttons optional">Remember me</label>
  <input type="hidden" name="user[remember_me]" value="" autocomplete="off" />
  <span class="radio">
    <label for="user_remember_me_true">
      <input
        value="true"
        class="radio_buttons optional"
        type="radio"
        name="user[remember_me]"
        id="user_remember_me_true"
      />Yes
    </label>
  </span>
  <span class="radio">
    <label for="user_remember_me_false">
      <input
        value="false"
        class="radio_buttons optional"
        readonly="readonly"
        type="radio"
        name="user[remember_me]"
        id="user_remember_me_false"
      />No
    </label>
  </span>
</div>
```

It's impressive that such a small piece of Ruby code gets you such a complete piece of HTML!

### HTML

From the previous example, we can see how Simple Form generates a lot of HTML for each field. That includes the HTML for the input field and a wrapper div around the label and input field. We can customize those by specifying a custom class and id using the `input_html` and `wrapper_html` attributes. Here is an example.

```erb
<%= simple_form_for @user do |f| %>
  <%= f.input :username, wrapper_html: { class: 'username' }, input_html { id: 'username' } %>
<% end %>
```

That's also a way to set attributes for the related HTML, such as `maxlength` or `value`. Let's take the password field from our user form. We can limit the size and length of the field with the `maxlength` attribute.

```erb
<%= f.input :password, input_html: { maxlength: 20 } %>
```

## Custom Inputs and Additional Options

Simple Form is a Ruby library that prepares HTML nodes for you. It comes with a whole set of fields, but you can also add your own. To do so, you can create classes inheriting from Simple Form's classes. Let's consider defining a custom social network input field with a little '@' prefix in front of the actual field.

```ruby
# app/inputs/social_handle_input.rb
class SocialHandleInput < SimpleForm::Inputs::Base
  def input(wrapper_options)
    merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)

    "@ #{@builder.text_field(attribute_name, merged_input_options)}".html_safe
  end
end
```

Now we can use it in the following way:

```erb
<%= f.input :network_handle, as: :social_handle %>
```

> Note that, if your User model doesn't have a `network_handle` attribute, you must add it through a migration or cheat with an `attr_accessor` in the model.

## i18n Support

As we've mentioned, building forms is often a pain point. But things get even more painful when it comes to websites and applications that support multiple languages. Simple Form, thankfully, follows Ruby on Rails standards. You can use a `simple_form` key in the local files to define translations for all labels, hints, placeholders, prompts, etc.

Here is a little example:

```ruby
en:
  simple_form:
    labels:
      user:
        username: 'User name'
        password: 'Password'
    hints:
      user:
        username: 'User name to sign in.'
        password: 'No special characters, please.'
    placeholders:
      user:
        username: 'Your username'
        password: '****'
    include_blanks:
      user:
        age: 'Rather not say'
    prompts:
      user:
        role: 'Select your role'
```

## Value Objects

Sometimes, we might use custom, non-ActiveRecord classes to instantiate the main object a form relies upon. This can be done to compose data from multiple models into a synthetic one for either business logic reasons or to gather several attributes into a single object.

Whatever the reason, you can still rely on Simple Form to build forms for that kind of object. To do so, the object class needs to implement, in the best case, three methods: `to_model`, `to_key`, and `persisted?`.

The `to_model` method will point to the object itself:

```ruby
def to_model
  self
end
```

The `to_key` allows us to point to the identifier attribute for the object — usually, that means an `id` attribute named:

```ruby
def to_key
  id
end
```

Finally, the `persisted?` method is there to tell Simple Form if the object is directly persisted or not.

```ruby
def persisted?
  false
end
```

If that method isn't present, the `f.submit` helper isn't usable.

There is a faster way, though — including the `ActiveModel::Model` module in the class:

```ruby
# app/models/account.rb
class Account
  include ActiveModel::Model

  attr_accessor :id, :company_name
end
```

And let's use it through the User model.

```ruby
# app/models/user.rb
class User < ApplicationRecord
  attr_accessor :network_handle

  def account
    @account ||= Account.new(id: 1, company_name: "Acme Inc.")
  end

  def company_name
    account.company_name
  end
end
```

We can then add a field for the company name within the user form:

```erb
<%= f.input :company_name %>
```

Of course, this attribute will not get saved in a table. However, the same concept can be applied to compose a value object with attributes pulled from several models.

## Wrapping Up

In this post, we've seen that Simple Form is easy to install and integrate into your Ruby on Rails application. Simple Form not only takes care of the details of each field in your form, but also generates complex and complete HTML so your form can be styled and shaped with ease.

I encourage you to dive into [Simple Form's documentation](https://www.rubydoc.info/gems/simple_form/1.3.1) to see how powerful it can be.

Happy coding!

**P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, [subscribe to our Ruby Magic newsletter and never miss a single post](https://blog.appsignal.com/ruby-magic)!**