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:
<%= 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.
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:
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.
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.
rails g controller users
# 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
# 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:
<!-- 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.
<!-- 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:
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 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:
<%= f.input :remember_me, input_html: { value: '1' }, as: :radio_buttons %>
This will generate the following 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.
<%= 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.
<%= 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.
# 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:
<%= 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 anattr_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:
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:
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:
def to_key
id
end
Finally, the persisted?
method is there to tell Simple Form if the object is directly persisted or not.
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:
# app/models/account.rb
class Account
include ActiveModel::Model
attr_accessor :id, :company_name
end
And let's use it through the User model.
# 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:
<%= 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 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!
Top comments (0)