Image
#forms #rails
Posted July 19, 2021 ‐  8 min read

Rails form : tutorial from the ground

Introduction : why a new tutorial about forms ?

"Form" is an old standard from the web, and handling form with Rails is almost as old as Rails itself. So why bother about another guide about forms ? They tend to be a lot more complex nowadays. You can submit them via plain browser request, or ajax, TurboDrive, TurboFrame, vanillaJS, and so on. They make the UX and data-flow not so easy to understand, so sometimes it's good to go back to basics :)

Tools used : Rails 6.1.3, Ruby 3

What is a form ?

A web form is not a Rails concept, a form is a way to send data to  a server. There are many other ways to achieve this, but using the <form> tag means using a standard way to send data. It means excellent device support, excellent accessibility, browser support, and so on.

<form action="/books" method="post">
   <input type="text" name="book[title]">
   <input type="submit">
</form>

This works. Without Rails. Without any JavaScript. The browser itself is able to send a page request to a URL.

So why is Rails needed anyway ?

At first glance we don't need Rails to send any data to the server.

But, to avoid redundant, error-prone copy/pasting, Rails comes with helper that will generate the above form automagically, plus :

 - authenticity_token for security reasons,
 - consistent classes and ids,
 - consistent "name" tags
 - ability to disable the submit button once pressed (via JavaScript library named Rails-ujs)
 - other goodies  (see this article, for example).

Rails has helpers to build forms :

Now let's see how Rails take care of the simple form  above :

<# This is what you write in your Rails template file #>
<%= form_with scope: "book", url: "/books" do |form| %>  
    <%= form.text_field :title %>  
    <%= form.submit "Create" %>  
<% end %>

<# This is the generated HTML you can view in your browser  #>
<form class="new_book" id="new_book" action="/books" method="post">
   <input name="utf8" type="hidden" value="✓">
   <input type="hidden" name="authenticity_token" value="…">
   <input type="text" name="book[title]" id="book_title">
   <input type="submit" name="commit" value="Create" data-disable-with="Create">
</form>

Here is what you can notice from here :

Using form without Rails helper is tedious and risky.

Tutorial from scratch

$> rails new myform --minimal
$> cd myform

inside app/controllers/welcome_controller.rb

class WelcomeController < ApplicationController

  # welcome_path  GET /welcome
  # root_path  GET /
  def index
  end

  # update_book_path  POST /welcome/update_book
  def update_book
  end

end

inside app/views/welcome/index.html.erb

<h1>Welcome ! This is a tutorial about Rails forms</h1>

<%= form_with scope: "book", url: update_book_path, method: :put do |form| %>  
  <%= form.text_field :title %>  
  <%= form.submit "Create" %>  
<% end %>

inside config/routes.rb

Rails.application.routes.draw do
  get "/welcome", to: "welcome#index"
  put "/welcome/update_book", to: "welcome#update_book", as: 'update_book'
  
  root "welcome#index"
end

Now run

$> bin/rails server

And open your browser at http://localhost:3000

Rendered form

When you're working with form in a Rails environment, I suggest always opening the Chrome dev tools console in order to see what's going on in the DOM Tree.

Maybe you'll see some surprises. Here, what we can see is :

Sending data to the server

Inside Chrome dev tools, open the Network tab. Then, type any name for a book inside the form, and submit the form by clicking the button.

Chrome devtools form submit

We can see that 4 parameters were sent to the server : _method, authenticity_token, book[title], commit

Now open your terminal :

Terminal parameters

There's only 3 parameters, _method has disappeared, because Rails used it to calculate which method of which Controller was targeted.

So Rails already called the right method for us : WelcomeController#update_book.

And pass 3 parameters into the "params" object (that can be used by any method of the controller) :

Pre-fill the form with default values

The "Rails way" to pre-fill the form with value is to use a Model, which is an object mixed to the database.

If you already have some coding experience, that sounds wrong. Actually, many tutorials do not advise to do so. Instead, you use a plain Model, not mapped to the database, named "form object". I will create a separate blog article about this soon, because this tutorial is already thick enough :)

By now you just have to know that it is possible to do so with Rails, and it brings even more magic (i.e. shorter code) to Rails form.

Unpacking data sent

inside app/controllers/welcome_controller.rb

class WelcomeController < ApplicationController

  # welcome_path  GET /welcome
  # root_path GET /
  def index
  end

  # update_book_path  POST  /welcome/update_book
  def update_book
    p '' 
    p '--- extracted params are ---' 
    p book_params # will output {"title" => "gatsby"}
    p '' 
  end

  def book_params
    params.require(:book).permit(:title).to_h
  end

end

Now put "gatsby" in the form and submit the button.

Here is what is printed inside the console.
unpacked_form

As you can see, there is no magic here, this time. Worse, you have to deal with "strong parameters", which are here for security reasons, but are somehow annoying because they add a lot of verbosity.

Conclusion

Help needed 😊

If you enjoyed the article, you can :

  • Share the article on Twitter , or LinkedIn , or Reddit , it will stimulate the writing effort, thanks !
  • Subscribe to the newsletter, you'll be warned each time a new Rails tutorial is released : we start from the rails new command to fully understand a new concept.
  • Subscribe to the Bootrails Beta, it's a tool to launch new Rails apps.

Thanks to all,

David