Skip to content

Latest commit

 

History

History
277 lines (221 loc) · 7.44 KB

forms.adoc

File metadata and controls

277 lines (221 loc) · 7.44 KB

Forms

The Data-Input Workflow

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

Request the people#new form

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:

app/controllers/people_controller.rb
# 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

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.

app/views/people/_form.html.erb
<%= 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="&#x2713;" />
<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.

Push 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

app/controllers/people_controller.rb
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.

Present the new Data

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

Generic Forms

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.

FormTagHelper

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.

Alternatives

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