Thursday, July 13, 2017

The Login Page in My Own Style

I continued my Ruby on Rails adventure for the login page. After I had read the chapter 8 of Michael Hartl's book. I decided to move the header part to the _header.html.erb partial file for the myapp2 project.

* First, I create a branch called basic-login.
$ git checkout -b basic-login

* I moved the debug message <%= debug(params) if Rails.env.development? %> from home.html.erb to application layout file application.html.erb
myapp2/app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag    'application', media: 'all',
                                              'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>
  <body>
    <div class="container">
      <% flash.each do |message_type, message| %>
        <div class="alert alert-<%= message_type %>"><%= message %></div>
      <% end %>       
    <%= yield %>
        <%= debug(params) if Rails.env.development? %>
    </div>
  </body>   
</html>

* I created a new file for the header: _header.html.erb on layout directory and I copied the content of <div> class "tm-header" to the header partial file.
myapp2/app/views/layouts/_header.html.erb
        <div class="tm-header">
            <div class="container-fluid">
                <div class="tm-header-inner">
                    <%= link_to("Sample App", root_path, :class => "navbar-brand tm-site-name") %>
            <!--        <a href="#" class="navbar-brand tm-site-name">Sample App</a>  -->
                   
                    <!-- navbar -->
                    <nav class="navbar tm-main-nav">

                        <button class="navbar-toggler hidden-md-up" type="button" data-toggle="collapse" data-target="#tmNavbar">
                            &#9776;
                        </button>
                       
                        <div class="collapse navbar-toggleable-sm" id="tmNavbar">
                            <ul class="nav navbar-nav">
                                <li class="nav-item active"><%= link_to "Home", root_path, class: "nav-link" %></li>
                                <li class="nav-item active"><%= link_to "Help", help_path, class: "nav-link" %></li>
                                <li class="nav-item active"><%= link_to "Log in", '#', class: "nav-link" %></li>
                            </ul>
                        </div>
                    </nav>
                   
                </div>                                 
            </div>           
        </div>

* I added the <%= render 'layouts/header' %> tag to the application layout file. It was placed after the <body> tag and before the <div class="container"> tag.
myapp2/app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag    'application', media: 'all',
                                              'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>
  <body>
    <%= render 'layouts/header' %>     
    <div class="container">
      <% flash.each do |message_type, message| %>
        <div class="alert alert-<%= message_type %>"><%= message %></div>
      <% end %>       
    <%= yield %>
        <%= debug(params) if Rails.env.development? %>
    </div>
  </body>   
</html>

* Similarly, I did it for the footer too. I created a footer partial file called: _footer.html.erb. And then, I copied the codes between the <footer class="tm-footer"> tag and </footer> to the _footer.html.erb partial file.
myapp2/app/views/layouts/_footer.html.erb
        <footer class="tm-footer">
            <div class="container-fluid">
                <div class="row">
                   
                    <div class="col-xs-12 col-sm-6 col-md-6 col-lg-3 col-xl-3">
                        <div class="tm-footer-content-box tm-footer-links-container">
                       
                            <h3 class="tm-gold-text tm-title tm-footer-content-box-title">Ruby on Rails Tutorial</h3>
                            <nav>
                                <ul class="nav">
                                    <li>
                                        <%= link_to("About", about_path, :class => "tm-footer-link") %>
                                    </li>
                                    <li>
                                        <%= link_to("Contact", contact_path, :class => "tm-footer-link") %>
                                    </li>
                                    <li>
                                        <a href="http://news.railstutorial.org/" class="tm-footer-link">News</a>
                                    </li>
                                </ul>
                            </nav>
                        </div>                     
                    </div>

                    <!-- Add the extra clearfix for only the required viewport
                        http://stackoverflow.com/questions/24590222/bootstrap-3-grid-with-different-height-in-each-item-is-it-solvable-using-only
                    -->
                    <div class="clearfix hidden-lg-up"></div>
                </div>

                <div class="row">
                    <div class="col-xs-12 tm-copyright-col">
                        <p class="tm-copyright-text">Jimmy Chong 2017</p>
                    </div>
                </div>
            </div>
        </footer>

* Afterward, I added the <%= render 'layouts/footer' %> line the the application layout file. It was placed between the <%= yield %> and the <%= debug() %> tag.
myapp2/app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag    'application', media: 'all',
                                              'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>
  <body>
    <%= render 'layouts/header' %>     
    <div class="container">
      <% flash.each do |message_type, message| %>
        <div class="alert alert-<%= message_type %>"><%= message %></div>
      <% end %>       
    <%= yield %>
      <%= render 'layouts/footer' %>       
        <%= debug(params) if Rails.env.development? %>
    </div>
  </body>   
</html>

* I went back to section 8.1.1 of the book: Sessions controller. I made a Sessions controller with method new.
$ rails generate controller Sessions new

* I copied the codes for the Sessions controller (Login controller).
myapp2/config/routes.rb
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'

* I updated the path for Sessions controller test file.
 myapp2/test/controllers/sessions_controller_test.rb
  test "should get new" do
    get login_path
    assert_response :success
  end

* I copied the codes for login page.
myapp2/app/views/sessions/new.html.erb
<% provide(:title, "Log in") %>
<h1>Log in</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(:session, url: login_path) do |f| %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

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

      <%= f.submit "Log in", class: "btn btn-primary" %>
    <% end %>

    <p>New user? <%= link_to "Sign up now!", signup_path %></p>
  </div>
</div>

* I copied the codes of Sessions controller.
myapp2/app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

  def new
  end

  def create

    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
     
  end

  def destroy
    log_out
    redirect_to root_url
  end   
   
end

* I generated an integration test for user login.
$ rails generate integration_test users_login

* I copied the codes for login test.
myapp2/test/integration/users_login_test.rb
  test "login with invalid information" do
    get login_path
    assert_template 'sessions/new'
    post login_path, params: { session: { email: "", password: "" } }
    assert_template 'sessions/new'
    assert_not flash.empty?
    get root_path
    assert flash.empty?
  end

* Then I ran the test for the login part only.
$ rails test test/integration/users_login_test.rb
Running via Spring preloader in process 5373
Run options: --seed 21529

# Running:

.

Finished in 0.602614s, 1.6594 runs/s, 6.6378 assertions/s.

1 runs, 4 assertions, 0 failures, 0 errors, 0 skips

* I kept going to section 8.2 Logging in, I added Sessions helper module into the Application controller.
myapp2/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include SessionsHelper   
end

* I copied the session helper codes.
myapp2/app/helpers/sessions_helper.rb
module SessionsHelper

  # Logs in the given user.
  def log_in(user)
    session[:user_id] = user.id
  end

  # Returns the current logged-in user (if any).
  def current_user
    @current_user ||= User.find_by(id: session[:user_id])
  end   

  # Returns true if the user is logged in, false otherwise.
  def logged_in?
    !current_user.nil?
  end   

  # Logs out the current user.
  def log_out
    session.delete(:user_id)
    @current_user = nil
  end   
   
end

* This part, Listing 8.19: Changing the layout links for logged-in users, was slightly difficult for creating an header with drop down menu using the customized CSS theme by templatemo. I needed to rewrite the changing the layout links for logged-in use for the templatemo theme.
 
* I re-wrote the header to fit the drop down menu and the CSS theme. This code does not change the style and color of the Account drop down menu. The pull down menu is not pretty. However, this HTML code makes the Ruby code works.
myapp2/app/views/layouts/_header.html.erb
                        <div class="collapse navbar-toggleable-sm" id="tmNavbar">
                            <ul class="nav navbar-nav">
                                <li class="nav-item"><%= link_to "Home", root_path, class: "nav-link" %></li>
                                <li class="nav-item"><%= link_to "Help", help_path, class: "nav-link" %></li>
                               
                                <% if logged_in? %>
                                  <li class="nav-item"><%= link_to "Users", '#', class: "nav-link" %></li>
                               
                                  <li class="nav-item dropdown">
                                    <a href="#" class="nav-item dropdown-toggle" data-toggle="dropdown"> Account <b class="caret"></b>
                                    </a>
                                    <ul class="dropdown-menu">
                                        <li><%= link_to "Profile", current_user %></li>
                                        <li><%= link_to "Settings", '#' %></li>
                                        <li class="nav-item divider"></li>
                                        <li>
                                            <%= link_to "Log out", logout_path, method: :delete %>
                                        </li>
                                    </ul>
                                </li>
                               
                            <% else %>                               
                                <li class="nav-item"><%= link_to "Log in", login_path, class: "nav-link" %></li>       
                            <% end %>
                               
                            </ul>
                        </div>



* I adjusted the height of the header banner to make it fits with the drop down menu.
myapp2/app/assets/stylesheets/templatemo-style.css
.tm-header-inner {
    display: -webkit-flex;
    display: -ms-flexbox;
    display: flex;
    -webkit-align-items: center;
        -ms-flex-align: center;
            align-items: center;
    -webkit-justify-content: space-between;
        -ms-flex-pack: justify;
            justify-content: space-between;
    height: 175px;
}

* I copied the file bootstrap.min.js to myapp2/app/assets/javascripts.

* But I did NOT change the application.js file.

* I added the resulting digest method in user.rb model file.
myapp2/app/models/users.rb
class User < ApplicationRecord
  before_save { self.email = email.downcase }
  validates :first_name, presence: true, length: { maximum: 50 }
  validates :last_name, presence: true,  length: { maximum: 50 }   
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i   
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }
   
  # Returns the hash digest of the given string.
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end   
   
end

* I created a user fixtur in user.yml.
myapp2/test/fixtures/users.yml
jimmy:
  first_name: Jimmy
  last_name: Chong
  email: jimmyc5@example.com
  password_digest: <%= User.digest('password') %>

* I copied the Integration Test codes to define new user "jimmy" and to test "login with valid information".
myapp2/test/integration/users_login_test.rb
require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:jimmy)
  end   
   
  test "login with invalid information" do
    get login_path
    assert_template 'sessions/new'
    post login_path, params: { session: { email: "", password: "" } }
    assert_template 'sessions/new'
    assert_not flash.empty?
    get root_path
    assert flash.empty?
  end   

  test "login with valid information" do
    get login_path
    post login_path, params: { session: { email:    @user.email,
                                          password: 'password' } }
    assert_redirected_to @user
    follow_redirect!
    assert_template 'users/show'
    assert_select "a[href=?]", login_path, count: 0
    assert_select "a[href=?]", logout_path
    assert_select "a[href=?]", user_path(@user)
  end   
       
end

* And then, I ran the integration test.
$ rails test test/integration/users_login_test.rb
Running via Spring preloader in process 5842
Run options: --seed 52302

# Running:

..

Finished in 0.711340s, 2.8116 runs/s, 14.0580 assertions/s.

2 runs, 10 assertions, 0 failures, 0 errors, 0 skips

* I added the line "log_in @user" in the Create method of Users Controller to let a new user log in automatically once he has signed up.
myapp2/app/controllers/users_controller.rb
  def create
    @user = User.new(user_params)
      if @user.save
      log_in @user         
      flash[:success] = "Welcome to the Sample App!"         
      redirect_to @user
    else
      render 'new'
    end
  end

* I added a Test Helper method: is_logged_in? to check if a user is logged in.
myapp2/test/test_helper.rb
  def is_logged_in?
    !session[:user_id].nil?
  end

* I added a line assert is_logged_in? in the Integration Test: users_signup_test.rb to test When a user has successfully sign up, the user will re-directed to log in automatically.
myapp2/test/integration/users_signup_test.rb
  test "valid signup information" do
    get signup_path
    assert_difference 'User.count', 1 do
      post users_path, params: { user: { first_name:  "Example",
                                         last_name: "User",
                                         email: "user@example.com",
                                         password:              "password",
                                         password_confirmation: "password" } }
    end
    follow_redirect!
    assert_template 'users/show'
    assert is_logged_in?
     
  end

* I added the "login with valid information followed by logout" method in the users_login_test.rb Integration Test.
myapp2/test/integration/users_login_test.rb
  test "login with valid information followed by logout" do
    get login_path
    post login_path, params: { session: { email:    @user.email,
                                          password: 'password' } }
    assert is_logged_in?
    assert_redirected_to @user
    follow_redirect!
    assert_template 'users/show'
    assert_select "a[href=?]", login_path, count: 0
    assert_select "a[href=?]", logout_path
    assert_select "a[href=?]", user_path(@user)
    delete logout_path
    assert_not is_logged_in?
    assert_redirected_to root_url
    follow_redirect!
    assert_select "a[href=?]", login_path
    assert_select "a[href=?]", logout_path,      count: 0
    assert_select "a[href=?]", user_path(@user), count: 0
  end

* Finally, I have completed Chapter 8 in my own CSS style. I ran a test, added all untrackted file, commited the changes, merged to Master branch in Git.
$ rails test
$ git add -A
$ git commit -m "Implement basic login"
$ git checkout master
$ git merge basic-login

* Then, I pushed the project to Github as well. My URL address for myapp2 project in Github is: https://github.com/jimmy2046/myapp2
$ rails test
$ git push

* The screen shot after a user has logged in successfully.


1 comment:

  1. It is nice blog Thank you provide important information and I am searching for the same information to save my time Ruby on Rails Online Training Hyderabad

    ReplyDelete

How to kill an abandoned process in Linux/Unix

I remembered it, then I forgot, then I remembered it, and then I forgot again. In case of a Linux/Unit process hang, I have to figure out ...