JBerczel's Notes on learning Web Development

Bare Metal Forms and Helpers Walkthrough

Estimated time: 2-3 hours

Course: Ruby on Rails >> Forms and Authentication >> Project: Forms

Objective

These projects will give you a chance to actually build some forms, both using nearly-pure HTML and then graduating to using the helper methods that Rails provides.

Basic Steps:

  1. Setup
  2. Build HTML Form
  3. Build #form_tag Form
  4. Build #form_for Form
  5. Editing Users
  6. Extra Credit
Disclaimer: This is my implementation of Project:Forms from the Odin Project. I'm no Rails Master, so do not take this walkthrough as gospel. If you see any errors or something that is incorrect, please feel free to contact me in the comments.

Step 1: Setup

First, create a basic Rails app with User Model and Controller.

From the command line, create a new Rails app:

jamies-air:~ jxberc$ rails new re-former

Create and Migrate User Model:

jamies-air:re-former jxberc$ rails generate model User username:string email:string password:string
jamies-air:re-former jxberc$ db:migrate

In app/models/user.rb, add validations for presence:

class User < ActiveRecord::Base
  validates :username, presence: true
  validates :email, presence: true
  validates :password, presence: true
end

In config/routes.rb, create routes for #new and #create actions:

ReFormer::Application.routes.draw do
  resources :users, :only => [:new, :create]
end

In console, check to make sure routes were created:

jamies-air:re-former jxberc$ rake routes
    Prefix Verb URI Pattern         Controller#Action
user_index POST /user(.:format)     user#create
  new_user GET  /user/new(.:format) user#new
jamies-air:re-former jxberc$

Generate a UsersController:

jamies-air:re-former jxberc$ rails generate controller Users

In app/controllers/users_controller.rb, write empty methods for #new and #create:

class UsersController < ApplicationController
  def new
  end

  def create
  end
end

In app/views/users folder, create new.html.erb file. Add boilerplate text to file.

From the command line, load development server using rails s. You should see the boilerplate text at http://localhost:3000/users/new.

Step 2: Build HTML Form

Build a form for creating a new user:

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

  <label for="username">Username:</label>
  <input id="username"  name="username" type="text"><br>

  <label for="email">e-mail:</label>
  <input id="email" name="email" type="text"><br>

  <label for="password">Password:</label>
  <input id="password" name="password" type="password"><br>

  <input type="submit" value="Submit">
</form>

If you try to submit data now you will receive an ActionController::InvalidAuthenticityToken error. Add authenticity token as follows:

<form accept-charset="UTF-8" action="/users" method="post">
  <input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">
  ...

Now if you try to submit data, you will get a template is missing error.

 template missing

That is okay. This means we've reached our #create action in the controller and by default it looks for an app/views/users/create.html.erb file.

Instead, lets build the #create action to go elsewhere:

def create
  @user = User.new(:username => params[:username], :email => params[:email], :password => params[:password])
  if @user.save
    redirect_to new_user_path
  else
    render :new
  end
end

In app/controllers/users_controller.rb, create and implement a #user_params helper method:

class UsersController < ApplicationController
  ...
  private

  def user_params
    params.require(:user).permit(:username, :email, :password)
  end

Update html form to submit hash of user parameters:

<form accept-charset="UTF-8" action="/users" method="post">
  ...
  <input id="username"  name="user[username]" type="text"><br>
  ...
  <input id="email" name="user[email]" type="text"><br>
  ...
  <input id="password" name="user[password]" type="password"><br>
  ...
  <input type="submit" value="Submit">
</form>

Update #create action with new helper method:

class UsersController < ApplicationController
  ...
  def create
    @user = User.new(user_params)
      if @user.save
        redirect_to new_user_path
      else
        render :new
      end
  end
  ...

Finally, confirm you can submit data using the form:

 html_form

Log output:

Started POST "/users" for 127.0.0.1 at 2014-05-05 17:56:21 -0400
Processing by UsersController#create as HTML
  Parameters: {"authenticity_token"=>"vapWanzKp+ZGIdvaE1HTcwS5ybQs/6pQJyuobJOcsmM=", "user"=>{"username"=>"thor", "email"=>"thor@email.com", "password"=>"[FILTERED]"}}
   (0.1ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "users" ("created_at", "email", "password", "updated_at", "username") VALUES (?, ?, ?, ?, ?)  [["created_at", Mon, 05 May 2014 21:56:21 UTC +00:00], ["email", "thor@email.com"], ["password", "thor"], ["updated_at", Mon, 05 May 2014 21:56:21 UTC +00:00], ["username", "thor"]]
   (0.6ms)  commit transaction
Redirected to http://localhost:3000/users/new
Completed 302 Found in 14ms (ActiveRecord: 2.4ms)

Step 3: Build #form_tag Form

In this step, we will replace our html form with a #form_tag Form.

First, comment out your html form:

<!-- <form accept-charset="UTF-8" action="/users" method="post">
  <input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">

  <label for="username">Username:</label>
  <input id="username"  name="user[username]" type="text"><br>

  <label for="email">e-mail:</label>
  <input id="email" name="user[email]" type="text"><br>

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

  <input type="submit" value="Submit">
</form> -->

Switch to using #form_tag and #*_tag helper methods. Note that Rails will insert authenticity token automatically.

<%= form_tag("/users", method: "post") do %>
  <%= label_tag(:username, "Username:") %>
  <%= text_field_tag(:username) %><br>

  <%= label_tag(:email, "email:") %>
  <%= text_field_tag(:email) %><br>

  <%= label_tag(:password, "password:") %>
  <%= password_field_tag(:password) %><br>

  <%= submit_tag("Submit") %>
<% end %>

In app/controllers/user_controller.rb, modify#create method to once again accept normal top level User attributes.

def create
    @user = User.new(:username => params[:username], :email => params[:email], :password => params[:password])
    #@user = User.new(user_params)
    ...
  end

Confirm you can submit data.

Step 4: Build #form_for Form

Modify your #new action in the controller to instantiate a blank User object and store it in an instance variable called @user.

class UsersController < ApplicationController
  def new
    @user = User.new
  end
  ...

Comment out #form_tag and build #form_for form:

<!-- <%= form_tag("/users", method: "post") do %>
  <%= label_tag(:username, "Username:") %>
  <%= text_field_tag(:username) %><br>

  <%= label_tag(:email, "email:") %>
  <%= text_field_tag(:email) %><br>

  <%= label_tag(:password, "password:") %>
  <%= password_field_tag(:password) %><br>

  <%= submit_tag("Submit") %>
<% end %> -->
<%= form_for @user do |f| %>
<p>
  <%= f.label :username %> <br/>
  <%= f.text_field :username %>
</p>
<p>
  <%= f.label :email %> <br/>
  <%= f.text_field :email, :value => "example@email.com" %>
</p>
<p>
  <%= f.label :password %> <br/>
  <%= f.password_field :password %>
</p>
<p><%= f.submit %></p>

Note: I've added a default placeholder value for :email.

Finally, in app/controllers/user_controller.rb, switch your controller's #create method to accept the nested :user hash from params.

def create
    #@user = User.new(:username => params[:username], :email => params[:email], :password => params[:password])
    @user = User.new(user_params)
    ...
  end

Confirm you can submit data using the newly created #form_for form:

form_for form

Step 5: Editing Users

Update your routes and controller to handle editing an existing user.

In config/routes.rb:

ReFormer::Application.routes.draw do
  resources :users, :only => [:new, :create, :edit, :update]
  ...

In app/controllers/users_controller.rb:

class UsersController < ApplicationController
  ...
  def edit
    @user = User.find(params[:id])
  end

  def update
    @user = User.find(params[:id])
    if 
      @user.update(user_params)
      redirect_to edit_user_path(@user)
    else
      render :edit
    end
  end

Create app/views/users/edit.html.erb. Copy/paste your form from the New view to Edit form:

<%= form_for @user do |f| %>
  USERNAME:<%= f.text_field :username %> <br />
  EMAIL:<%= f.text_field :email, :value => "example@email.com" %> <br />
  PASSWORD: <%= f.password_field :password %><br />
  <%= f.submit %>
<% end %>

"View source" on the form generated by #form_for in your Edit view.

hidden fields

You should see authentication token and other relevant hidden fields.

Confirm that you can submit data and that validations are working.

Step 6: Extra Credit

In app/views/users/new.html.erb and app/views/users/edit.html.erb, include error messages:

<%= form_for @user do |f| %>
<ul>
  <% @user.errors.full_messages.each do |error| %>
  <li><%= error %></li>
  <% end %>
</ul>
...

Now, when validations fail, errors should display:

display_errors

Congratulations! You've just created basic forms using html and ruby helper methods.

comments powered by Disqus