A guide to creating forms in Ruby on Rails 6 
Tags: ror, rails, webdev, backend
Published: 2019-10-27 18:18:40

---
title: A guide to creating forms in Ruby on Rails 6 
published: true
description: 
tags: ROR, Ruby On Rails, web development, backend
---

In any app, forms serve the most primary way for data creation and modification and Ruby on Rails (from here on called Rails) app is no different. 

In this guide, we will look at different ways of creating forms in Rails.  

# A High-Level Overview

At a high level, there are two ways of creating forms in Rails.

1) We can create the entire form by hand using HTML just as we would in any static HTML file.

2) Alternatively, we can use a powerful `form_with` helper that makes our lives much easier. This helper can have the following use cases.

- It can be used to set up a form for a resource that is associated with an Active Record model meaning it has REST routes and also a database model. It can be both for resource creation or update. 
 
- It can be used to create a form for a resource that doesn't have any Active Record model, meaning it has REST routes but no database model.
 
- It can be used for setting up a form having no resource and Active Record model, meaning there are neither REST routes nor any database model. Such a form can also be created by hand as point 1 illustrates but using `form_with` makes it easier still. 

Now let's explore form creation in little more detail.


## Form created with regular HTML markup

If you have done any HTML, then this one is the simplest and the most straightforward way of creating a form in Rails.  Here the form is created with the usual HTML markup.

```html
<form method="post" action="/users">
  <label for="email">Email</label>
  <input type="email" name="email" id="email">

  <label for="password">Password</label>
  <input type="password" name="password" id="password">
  
  <input type="submit" name="Sign up">
</form>
```

In such a form you define everything by yourself and don't take any help from Rails. You have to declare `method="post"` and set the form submission route `action="/users"` yourself. So far there is nothing Rails specific here.

## Form Created with `form_with` method (The Rails Way)

Rails conveniently offer us a `form_with` method to help us in creating a form. With this method, we leverage the Rails magic. 

Let's try to understand how this method works. 

## `form_with` in action

Suppose we have a User model with email and password attributes. If we want to create a user signup form (resource creation) this is how the form would look like.

```ruby
 <%= form_with(model: @user, local: true) do |f| %>

  <%= f.label :email %>
  <%= f.email_field :email %>

  <%= f.label :password %>
  <%= f.password_field :password %>

  <%= f.submit "Sign Up" %>
  
<% end %>
```

When erb is converted to HTML through Rails asset pipeline it produces:

```html
<form action="/users" accept-charset="UTF-8" method="post">

  <label for="user_email">Email</label>
  <input type="email" name="user[email]" id="user_email">

  <label for="user_password">Password</label>
  <input type="password" name="user[password]" id="user_password">

  <input type="submit" name="commit" value="Sign Up" data-disable-with="Sign Up">

</form>
```

I have skipped the input `type="hidden"` `name="authenticity_token"` tag which Rails add automatically to every form to prevent CSRF attacks.

Let's dissect the form bit by bit!

`form_with` is a method to which we have provided a configuration options hash. `model: @user` defines for which model we want to create the form. Or in other words which object will drive the form creation. 

`local: true` configures the form submission request to be local form request. In the absence of this option, Rails configures the form to send a remote XHR request on submission.

`form_with` automatically sets the `method="post"` and the submission route `action="/users"`. But how do Rails know which route to submit the form to and what method (HTTP Verb) to use? 

Let's understand this now.
 
## `form_with` method with `POST` request (resource creation)

If the user is signing up for the first time meaning the user doesn't exist in the database yet, then the form will be served by the `User#new` action in which we initialize `@user = User.new`.

This `@user` will be available in the `User#new` view which contains sign up form. Currently, all `@user` attributes are set to `nil`, since we didn't initialize attributes in `User.new`.

Thus when we set `model: @user`, Rails checks if any of the attributes of `@user` have a value other than `nil`. If all the fields are `nil` then it infers this user (resource) will be created for the first time, so it correctly sets the `method="post"`. 

But what about the route? 

In order to set the route rails calls `@user.class` to determine the Model class name which is `User`, looks for a similarly named Controller class `User` and maps to the correct action at which the user will be created, which would be `User#create` for which method is `POST` and route is `/users`. And that is how it finds out where to submit this form. 

If you name instance variable something else, such as `@foobar = User.new` and set `model: @foobar` it would still correctly determine the submission route by calling `@foobar.class` which would still be `User`. So instance variable name doesn't matter. 

But if the Rails automatically inferred route is not where you want to submit the form, you can always use `url: {whatever the submission route}`. With this attribute to the `form_with` you have full control over the submission route, in case you need it.


## `form_with` method with `PATCH` request (resource update)

The second case is where you are creating a user edit form. Here when you click a particular user link that needs an edit, the request will be mapped to `User#edit` where you will fetch the user as `@user = User.find(params[:id])`. This `@user` will be available in the `User#edit` view.

But since this time `@user` attributes have values, rails prepopulate corresponding form fields with available values taking care of not populating password field, so it filters that out. 

Also because `@user` attributes have values (even if one attribute has a value other than nil) rails infers that it is not a new resource meaning you are not creating it for the first time but instead you are looking to modify an existing resource. 

So it sets up the route properly by again calling the `@user.class` to determine Model, find its namesake Controller `User` and map to `User#update` which deals with updating a user, having the route `users/:id` with a `PATCH` request.

But, there is a problem the browser doesn't natively support the `PATCH` request it only supports `POST` AND `GET`. 

Rails achieve that by inserting the following tag inside of the form

```html
<input type="hidden" name="_method" value="patch">
```

How does that work? 

`POST` simply allows the browser to send data to the server. Form submission piggybacks that to transport data to the server but after the data is transferred it needs to tell Rails that it's not a `POST` data but a `PATCH` data and it accomplishes that by using such an input tag.

It simply acts as a label to tell Rails that it's a patch request and use incoming data as an edit data, not a creation data. 

When such a request hits the Rails app, it looks at the form find the hidden input field with a `value="patch"` and routes it to the `User#update` which handles `PATCH` request. So a lot of work is done by Rails. 

Let's take some other intricacies about the `form_with` method

### Form submitting to itself (GET and POST route are the same)

If you create a random instance variable such as `@have_no_class` that has no associated Model. So when Rails try to determine the submission route it calls `@have_no_class.class` it gets `NilClass` and since it couldn't correctly infer the submission route for this resource it would set the route to itself. 

So if you landed on the page with `GET` request to `'/signup'` then it would submit the form to itself at `'/signup'` with `POST`. Unless the Rails did actually determine the route and it turned out to be the same as the `GET` route.

### form_with gives a FormBuilder object

`form_with` takes an options hash but it also takes a block and passes a FormBuilder object as a block variable. 

```ruby
<%= form_with(model: @user, local: true) do |f| %>
```

`|f|` is the FormBuilder object (you can name it anything you want) that helps us in setting up form fields. It has methods for creating all the form elements such as radio button, text-area, check-box, input etc.

So if you want to create an input of `type=password` you would do 

```ruby
<%= f.label :password %>
<%= f.password_field :password %>
``` 
It will produce

```html
<label for="user_password">Password</label>
<input type="password" name="user[password]" id="user_password">

```
Here we gave `:password` symbol as an argument to the `f.label` method which it uses to set up the label text and `for="user_password"`. `for` is set to `user_password` and not `password` because we set `model: @user` so it prefixes it with the name of the model. 

Similarly in the input tag for password, it sets the `id="password"`, `type="password"` and `name="user[password]"`. In order to understand `user[password]` we have to look at the `params hash`.

### A look at the `Params` hash

You have to be mindful of the fact the Rails use the `params` hash for sending any data that is being transferred with the request, be it dynamic URL segment like `/:id` or query param like `?page=1` or the data sent via `POST` or `PATCH` request, all are available inside the `params` hash. In simple words params contain some information about the incoming request. 

What Rails does is that it sets up `user` hash inside of the `params` hash and inside the `user` hash it sets up all the form name, value pairs. 

When the form is submitted it takes all the form values, stuffs them in the `user` hash of the params hash and sends it to the server. 

```ruby
params: {
  user: {
    email: 'foobar@baz.com',
    password: 'foobar'
  }
}

```

That is why you see the `name="user[password]"` because it is setting up the `password` field on the `user` hash and setting its value to whatever is typed into the field. But this is all done behind the scenes by Rails.

Now coming back to the FormBuilder object `|f|`.

You can additionally set up any other attribute on input password tag (or any other form field) by passing in the options hash such as setting up the `class` attribute.

```ruby
<%= f.password_field :password, class: 'form-control' %>
```

FormBuilder object, in our case `|f|` has convenient methods to which you pass configurations and it produces the desired form element for you.


## `form_with` method for a non-model resource

`form_with` can also be used to set up a form for a resource that has no Model. It means it has some or all of the REST routes but no Model to persist the data. A classic example would be creating Session as a REST resource but with no Model, because data is sent to the browser and not stored on the server.

In such a form creation you don't say `model: @session` because there is no Model to instantiate the object which can be passed into view. Instead, we use `scope: 'session'`. This does all the Rails magic steps as discussed earlier.

Before we have been using `model` attribute now we use `scope`. We use `model` when we have the Model whose instance is going to drive the creation of the form.

With Session since we don't have a Model we don't use `model` attribute. Instead, we use `scope` that sets up the `session` hash inside of `params` hash and all the form values will be stuffed inside of the `session` hash.

Since this time we don't have an instance variable to determine the class and the route, the form submits to itself (same route it is served from).


# Key Takeaways 

 - If you have a form to either create or update a resource that happens to have an associated Model as well, use `from_with` and set `model: {whatever the instance varaible}`.

 - If you have a form for a resource that does not have an associated Model, use `from_with` and set `scope: {whatever the scope name you want in params hash}`.

 - If you want to create a non-resource form, you can do so with a `form_with` in which case you would have to define `url: '{whatever form submission route}'`. Or you can create the form by hand, the good-ol way.