To understand forms we take a look at the data workflow. Understanding it better will help to understand the work of forms.
Example application:
$ rails new testapp
[...]
$ cd testapp
$ rails generate scaffold Person first_name last_name
[...]
$ rails db:migrate
[...]
$ rails server
[...]
Most times we create forms by using the Scaffold. Let’s go through the flow the data
When we request the http://localhost:3000/people/new URL the router answers the following route:
new_person GET /people/new(.:format) people#new
The controller app/controllers/people_controller.rb
runs this code:
# GET /people/new
def new
@person = Person.new
end
So a new Instance of Person
is created and stored in the instance
variable @person
.
Rails takes @person
and starts processing the view file
app/views/people/new.html.erb
<h1>New Person</h1>
<%= render 'form', person: @person %>
<%= link_to 'Back', people_path %>
render 'form'
renders the file app/views/people/_form.html.erb
and sets the local variable person
with the content of @person
.
<%= form_with(model: person, local: true) do |f| %>
<% if person.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(person.errors.count, "error") %> prohibited this person from being saved:</h2>
<ul>
<% person.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :first_name %>
<%= f.text_field :first_name %>
</div>
<div class="field">
<%= f.label :last_name %>
<%= f.text_field :last_name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
form_with(model: person, local: true)
embeddeds the
two text_fields
:first_name
and :last_name
plus a submit
Button.
The resulting HTML:
[...]
<form action="/people" accept-charset="UTF-8" method="post">
<input name="utf8" type="hidden" value="✓" />
<input type="hidden" name="authenticity_token" value="lSt...hbIg==" />
<div class="field">
<label for="person_first_name">First name</label>
<input type="text" name="person[first_name]" />
</div>
<div class="field">
<label for="person_last_name">Last name</label>
<input type="text" name="person[last_name]" />
</div>
<div class="actions">
<input type="submit" name="commit" value="Create Person"
data-disable-with="Create Person" />
</div>
</form>
[...]
This form uses the post
method to upload the data to the server.
We enter "Stefan" in the first_name
field and "Wintermeyer" in the
last_name
field and click the submit button. The browser uses the post
method to uploads the data to the URL /people
. The log shows:
Started POST "/people" for 127.0.0.1 at 2017-03-24 18:20:03 +0100
Processing by PeopleController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Vc...EAg==",
"person"=>{"first_name"=>"Stefan", "last_name"=>"Wintermeyer"},
"commit"=>"Create Person"}
(0.2ms) begin transaction
SQL (1.1ms) INSERT INTO "people" ("first_name", "last_name",
"created_at", "updated_at") VALUES (?, ?, ?, ?) [["first_name", "Stefan"],
["last_name", "Wintermeyer"], ["created_at", "2017-03-24 17:20:03.600013"],
["updated_at", "2017-03-24 17:20:03.600013"]]
(1.0ms) commit transaction
Redirected to http://localhost:3000/people/1
Completed 302 Found in 6ms (ActiveRecord: 2.3ms)
What happened in Rails?
The router answers the request with this route
POST /people(.:format) people#create
The controller app/controllers/people_controller.rb
runs this code
def create
@person = Person.new(person_params)
respond_to do |format|
if @person.save
format.html { redirect_to @person, notice: 'Person was successfully created.' }
format.json { render :show, status: :created, location: @person }
else
format.html { render :new }
format.json { render json: @person.errors, status: :unprocessable_entity }
end
end
end
[...]
# Never trust parameters from the scary internet, only allow the white list through.
def person_params
params.require(:person).permit(:first_name, :last_name)
end
A new instance variable @person
is created. It represents a new Person
which was created with the params that were send from the browser to the
Rails application. The params are checked in the person_params
method
which is a whitelist. That is done so the user can not just inject
params which we don’t want to be injected.
Once @person
is saved a redirect_to @person
is triggered. That would
be http://localhost:3000/people/1 in this example.
The redirect to http://localhost:3000/people/1 is traceable in the log file
Started GET "/people/1" for 127.0.0.1 at 2017-03-24 18:20:03 +0100
Processing by PeopleController#show as HTML
Parameters: {"id"=>"1"}
Person Load (0.2ms) SELECT "people".* FROM "people"
WHERE "people"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Rendering people/show.html.erb within layouts/application
Rendered people/show.html.erb within layouts/application (1.0ms)
Completed 200 OK in 17ms (Views: 11.6ms | ActiveRecord: 0.2ms)
The router answers to this request with
person GET /people/:id(.:format) people#show
Which gets handled by the show method in
app/controllers/people_controller.rb
A form doesn’t have to be hardwired to an ActiveRecord object. You can
use the form_tag
helper to create a form by yourself. I use the
example of http://guides.rubyonrails.org/form_helpers.html
(which is the official Rails guide about forms) to show how to create
a search form which is not connected to a model:
<%= form_with(url: '/search') do |f| %>
<%= f.label(:q, "Search for:") %>
<%= f.text_field(:q, id: :q) %>
<%= f.submit("Search") %>
<% end %>
It results in this HTML code:
<form accept-charset="UTF-8" action="/search" method="get">
<label for="q">Search for:</label>
<input id="q" name="q" type="text" />
<input name="commit" type="submit" value="Search" />
</form>
To handle this you’d have to create a new route in config/routes.rb
and write a method in a controller to handle it.
There is not just a helper for text fields. Have a look at the official API documentation for all FormTagHelpers at http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html to get an overview. Because we use Scaffold to create a form there is no need to memorize them. It is just important to know where to look in case you need something else.
Many Rails developer use Simple Form as an alternative to the standard way of defining forms. It is worth a try because you can really safe time and most of the times it’s just easier. Simple Form is available as a Gem at https://github.com/plataformatec/simple_form