Saturday, July 15, 2017

Completed Ch 10: Updating, showing, and deleting user (Part 1)

* First, I started the Rails server for the sample_app project.
~$ cd sample_app
~/sample_app$ rails s -b 0.0.0.0 -p 3000

* And I viewed the homepage using Firefox: http://localhost:3000/

* Then, I started to follow the Chapter 10 of the books: Updating, showing, and deleting users.
https://www.railstutorial.org/book/updating_and_deleting_users

* I created an updating-users topic branch.
$ git checkout -b updating-users

* I added some words "4th edition (online version)" in the README.md file
sample_app/README.md
This is the sample application for
[*Ruby on Rails Tutorial:
Learn Web Development with Rails (4th Edition, online version)*](http://www.railstutorial.org/)
by [Michael Hartl](http://www.michaelhartl.com/).

* I added an edit method for the Users Controller.
sample_app/app/controllers/users_controller.rb
  def edit
    @user = User.find(params[:id])
  end

* I created the Edit User HTML page and copied the codes.
sample_app/app/views/users/edit.html.erb
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>

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

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

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Save changes", class: "btn btn-primary" %>
    <% end %>

    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="http://gravatar.com/emails" target="_blank">change</a>
    </div>
  </div>
</div>

* I added the edit_user_path(current_user) path for the Settings menu in the Header.
sample_app/app/views/layouts/_header.html.erb
<li><%= link_to "Settings", edit_user_path(current_user) %></li>

* I added an update method in Users Controller.
sample_app/app/controllers/users_controller.rb
  def update
    @user = User.find(params[:id])
    if @user.update_attributes(user_params)
      flash[:success] = "Profile updated"
      redirect_to @user
    else
      render 'edit'
    end
  end

* I generated Integration Test for unsuccessful user edit.
$ rails generate integration_test users_edit

* I copied the codes for unsuccessful user edit test.
sample_app/test/integration/users_edit_test.rb
require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "unsuccessful edit" do
    get edit_user_path(@user)
    assert_template 'users/edit'
    patch user_path(@user), params: { user: { name:  "",
                                              email: "foo@invalid",
                                              password:              "foo",
                                              password_confirmation: "bar" } }

    assert_template 'users/edit'
  end   

end

* Then, I ran the test.
$ rails test
Running via Spring preloader in process 26190
Run options: --seed 64799

# Running:

.............................

Finished in 1.333905s, 21.7407 runs/s, 51.7278 assertions/s.

29 runs, 69 assertions, 0 failures, 0 errors, 0 skips

* I added a test case called: "successful edit" in User Edit Integration Test.
sample_app/test/integration/users_edit_test.rb
  test "successful edit" do
    get edit_user_path(@user)
    assert_template 'users/edit'
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { name:  name,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name,  @user.name
    assert_equal email, @user.email
  end

* I added two lines of code showing successful "Profile updated" Flash message when an update is success.
sample_app/app/controllers/users_controller.rb
  def update
    @user = User.find(params[:id])
    if @user.update_attributes(user_params)
      flash[:success] = "Profile updated"
      redirect_to @user
    else
      render 'edit'
    end
  end

* I added "allow nil" input attribute for password validate (confirmation) field.
sample_app/app/models/user.rb
  validates :password, presence: true, length: { minimum: 6 }, allow_nil: true

* I tested to update a user profile with a user with USER ID == 1.

* In Firefox, I typed: http://localhost:3000/users/1/edit. And it brought me to the Update profile screen.


* I changed the user profile to update those fields respectively.
Name: Jimmy Chong 6.0
Email: jimmychong60@example.com
Password:
Confirmation

* Then, I clicked the "Save changes" button. It brought me to "Profile updated" successfully screen.

* Next, in Users Controller, I added before action that only logged in users are allowed to do "edit" and "update".

* On the bottom part of the Users Controller that stores private methods, I added def logged_in_user method to remind the user to login if they want to do "edit" or "update".

* I tested the codes by typing: localhost:3000/users/1/edit in Firefox. Ruby on Rails prompted me to Login before doing an "edit" if a user forgot to login before updating.

* In User Edit Integration Test, I added the log_in_as(@user) for the two test cases "unsuccessful edit" and "successful edit".
sample_app/test/integration/users_edit_test.rb
require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "unsuccessful edit" do
    log_in_as(@user)
    get edit_user_path(@user)
    assert_template 'users/edit'
    patch user_path(@user), params: { user: { name:  "",
                                              email: "foo@invalid",
                                              password:              "foo",
                                              password_confirmation: "bar" } }

    assert_template 'users/edit'
  end   

  test "successful edit" do
    log_in_as(@user) 
    get edit_user_path(@user)
    assert_template 'users/edit'
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { name:  name,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name,  @user.name
    assert_equal email, @user.email
  end   
   
end

* In User Controller Test, I added 2 test cases "should redirect edit when not logged in" and "should redirect update when not logged in" to make sure Ruby on Rails will re-direct the user if he attempts to do "edit" or "update" without login.
sample_app/test/controllers/users_controller_test.rb
require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end
   
    test "should get new" do
      get signup_path
      assert_response :success
  end

  test "should redirect edit when not logged in" do
    get edit_user_path(@user)
    assert_not flash.empty?
    assert_redirected_to login_url
  end

  test "should redirect update when not logged in" do
    patch user_path(@user), params: { user: { name: @user.name,
                                              email: @user.email } }
    assert_not flash.empty?
    assert_redirected_to login_url
  end   
   
end

* I added a second user named "Sterling Archer" in the User Test Fixture.
sample_app/test/fixtures/users.yml
archer:
  name: Sterling Archer
  email: duchess@example.gov
  password_digest: <%= User.digest('password') %>

* I added the second user to User Controller Test Suite
sample_app/test/controllers/users_controller_test.rb
  def setup
    @user = users(:michael)
    @other_user = users(:archer)     
  end

* In the User Controler, I added only correct_user can do an "update" or "edit". and I added a new action called def correct_user to confirm whether an input user is the correct user.
sample_app/app/controllers/users_controller.rb
before_action :correct_user, only: [:edit, :update]
# Confirms the correct user.
def correct_user
   @user = User.find(params[:id])
   redirect_to(root_url) unless @user == current_user
end

* In Sesssions Helper, I added a new action called def current_user?(user) to check if the given user is the current user.
sample_app/app/helpers/sessions_helper.rb
  # Returns true if the given user is the current user.
  def current_user?(user)
    user == current_user
  end

* After I had added a new action in Sessions Helper, I re-wrote the codes for the action def correct_user in Uses Controller.
sample_app/app/controllers/users_controller.rb
    # Confirms the correct user.
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless current_user?(@user)
    end

* In User Edit Integration Test Suite, I added a test case "successful edit with friendly forwarding".
sample_app/test/integration/users_edit_test.rb
  test "successful edit with friendly forwarding" do
    get edit_user_path(@user)
    log_in_as(@user)
    assert_redirected_to edit_user_url(@user)
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { name:  name,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name,  @user.name
    assert_equal email, @user.email
  end

* In the Sessions Helper module, I added two new actions, def redirect_back_or(default) and  def store_location.

* In Users Controller, I added the store_location action in the def logged_in_user method.
sample_app/app/controllers/users_controller.rb
    # Confirms a logged-in user.
    def logged_in_user
      unless logged_in?
        store_location
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end

* In Sessions controller, I updated an line redirect_back_or user in the create action/
sample_app/app/controllers/sessions_controller.rb
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      params[:session][:remember_me] == '1' ? remember(user) : forget(user)
      redirect_back_or user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end     
  end

* I added a test case, "should redirect index when not logged in" in User Controller Test Suite.
sample_app/test/controllers/users_controller_test.rb
  test "should redirect index when not logged in" do
    get users_path
    assert_redirected_to login_url
  end

* In User Controller, I added a before action that only logged_in_user can do "index", "edit", and
"update". And, I added a new index method as well.
sample_app/app/controllers/users_controller.rb
  before_action :logged_in_user, only: [:index, :edit, :update]
  before_action :correct_user,   only: [:edit, :update]

  def index
    @users = User.all     
  end

* I created a new index.html.erb file to list out all users in Ruby on Rails database.
sample_app/app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, size: 50 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

* I updated the gravatar_for action in the Users Helper.
sample_app/app/helpers/users_helper.rb
module UsersHelper

  # Returns the Gravatar for the given user.
  def gravatar_for(user, options = { size: 80 })
    gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
    size = options[:size]
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end
   
end

* I defined the attribute for class .users in the CSS stylesheet.
sample_app/app/assets/stylesheets/custom.scss
/* Users index */

.users {
  list-style: none;
  margin: 0;
  li {
    overflow: auto;
    padding: 10px 0;
    border-bottom: 1px solid $gray-lighter;
  }
}

* I added the path users_path that forwards to a page that lists out all users in the database when a user clicks the Users Listed Item on the Header.
sample/app/views/layouts/_header.html.erb
<li><%= link_to "Users", users_path %></li>

* In Firefox, I typed http://localhost:3000/users to test the function to lists out all users.

Friday, July 14, 2017

Use Git to Undelete Accidentally Deleted Files

One of the benefits of using Git for version control is that Git can recover accidental deleted files and accidental changes.

For example, if I changed a file named: sessions_helper_test.rb  by mistake. I can type the status of the changes file using the git status command.

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   test/helpers/sessions_helper_test.rb

no changes added to commit (use "git add" and/or "git commit -a")

I can undo my changes such as deleted file using the git checkout -f command.
$ git checkout -f
Your branch is up-to-date with 'origin/master'.

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean

My own style RoR project with Remember me feature

* Now it is the time for me to create my own style Ruby on Rails project with "Remember me" function.

* In the Bracket text editor, I switched the project to "myapp2".

* As usual, I started the Rails server using the rails server command.
~$ cd myapp2
~/myapp2$ rails s -b 0.0.0.0 -p 3000

* I went to the homepage of the project. In Firefox, I typed: http://localhost:3000/


* I opened a new Linux shell terminal, tested, tested the project, and made sure no failures.

~$ cd myapp2
~/myapp2$ rails test
Running via Spring preloader in process 20987
Run options: --seed 11214

# Running:

...........................

Finished in 1.639271s, 16.4707 runs/s, 40.2618 assertions/s.

27 runs, 66 assertions, 0 failures, 0 errors, 0 skips

* I went back to the book https://www.railstutorial.org/book/advanced_login

* I created a branch "advanced-login"

* I fixed some typos in the READMD.md file
/myapp2/README.md
For this version, I added this line in the Gemfile to make the test runs properly.

* I added the remember_digest field in the User model.
~/myapp2$ rails generate migration add_remember_digest_to_users remember_digest:string

* Here was the database migration file.
class AddRememberDigestToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :remember_digest, :string
  end
end

* Then, I ran db migration command.
$ rails db:migrate
== 20170714235540 AddRememberDigestToUsers: migrating =========================
-- add_column(:users, :remember_digest, :string)
   -> 0.0222s
== 20170714235540 AddRememberDigestToUsers: migrated (0.0231s) ================

* I copied and pasted (with some modifications) the User model file.
myapp2/app/models/user.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   

  # Returns the hash digest of the given string.
  def self.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
    BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end   

  # Returns a random token.
  def self.new_token
    SecureRandom.urlsafe_base64
  end   
   
  # Returns a random token.   
  def User.new_token
    SecureRandom.urlsafe_base64
  end   

  # Remembers a user in the database for use in persistent sessions.
  def remember
    self.remember_token = User.new_token
    update_attribute(:remember_digest, User.digest(remember_token))
  end

  # Returns true if the given token matches the digest.
  def authenticated?(remember_token)
    return false if remember_digest.nil?     
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end   

  # Forgets a user.
  def forget
    update_attribute(:remember_digest, nil)
  end   
   
end

* I copied and pasted the codes in 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
      params[:session][:remember_me] == '1' ? remember(user) : forget(user)
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
     
  end

  def destroy
    log_out if logged_in?
    redirect_to root_url
  end   
   
end

* I copied and pasted the codes in Sessions Helper.
myapp2/app/helpers/sessions_helper.rb
module SessionsHelper

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

  # Remembers a user in a persistent session.
  def remember(user)
    user.remember
    cookies.permanent.signed[:user_id] = user.id
    cookies.permanent[:remember_token] = user.remember_token
  end

  # Forgets a persistent session.
  def forget(user)
    user.forget
    cookies.delete(:user_id)
    cookies.delete(:remember_token)
  end   
       
  # Returns the current logged-in user (if any).
  def current_user
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id])       
      user = User.find_by(id: user_id)
      if user && user.authenticated?(cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
  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
    forget(current_user)     
    session.delete(:user_id)
    @current_user = nil
  end   
   
end

* I copied and pasted the codes for Users Login Integration Test.
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   

  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
    # Simulate a user clicking logout in a second window.
    delete logout_path     
    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   

  test "login with remembering" do
    log_in_as(@user, remember_me: '1')
    assert_not_empty cookies['remember_token']
  end

  test "login without remembering" do
    # Log in to set the cookie.
    log_in_as(@user, remember_me: '1')
    # Log in again and verify that the cookie is deleted.
    log_in_as(@user, remember_me: '0')
    assert_empty cookies['remember_token']
  end
   
end

* I added a test case: "authenticated? should return false for a user with nil digest"  the User Models Test.
myapp/test/models/user_test.rb
  test "authenticated? should return false for a user with nil digest" do
    assert_not @user.authenticated?('')
  end

*  I added a checkbox control for the Login page in Ruby Embedded Html file (html.erb) file.
myapp2/app/views/sessions/new.html.erb
      <%= f.label :remember_me, class: "checkbox inline" do %>
        <%= f.check_box :remember_me %>
        <span>Remember me on this computer</span>
      <% end %>

* And I added the properties of class .checkbox and id #session_remember_me  in the CSS stylesheet file as well.
app/assets/stylesheets/templatemo-style.css
.checkbox {
  margin-top: 10px;
  margin-bottom: 10px;
  span {
    margin-left: 20px;
    font-weight: normal;
  }
}

#session_remember_me {
  width: auto;
  margin-left: 10;
}

* I copied and pasted the codes in Test Helper.
myapp2/test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'

class ActiveSupport::TestCase
  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
  fixtures :all
  include ApplicationHelper  

  # Add more helper methods to be used by all tests here...
  
  # Returns true if a test user is logged in.
  def is_logged_in?
    !session[:user_id].nil?
  end

  # Log in as a particular user.
  def log_in_as(user)
    session[:user_id] = user.id
  end          
end


class ActionDispatch::IntegrationTest

  # Log in as a particular user.
  def log_in_as(user, password: 'password', remember_me: '1')
    post login_path, params: { session: { email: user.email,
                                          password: password,
                                          remember_me: remember_me } }
  end
end

* I created a new file sessions_helper_test.rb
~/myapp2$ touch test/helpers/sessions_helper_test.rb

* I copied and pasted the codes in it.
myapp2/test/helpers/sessions_helper_test.rb
require 'test_helper'

class SessionsHelperTest < ActionView::TestCase

  def setup
    @user = users(:jimmy)
    remember(@user)
  end

  test "current_user returns right user when session is nil" do
    assert_equal @user, current_user
    assert is_logged_in?
  end

  test "current_user returns nil when remember digest is wrong" do
    @user.update_attribute(:remember_digest, User.digest(User.new_token))
    assert_nil current_user
  end
end

* Lastly, I ran a test for the project.
$ rails test
Running via Spring preloader in process 23052
Run options: --seed 45355

# Running:

..............................

Finished in 1.240612s, 24.1816 runs/s, 57.2298 assertions/s.

30 runs, 71 assertions, 0 failures, 0 errors, 0 skips

* After the test command, I ran the test manually to verify the "Remember me" functionality work as the intention.


* To wrap to, I ran the test again, added all untracked files, committed changes, merged into Master branch, and pushed onto Github.
$ rails test
$ git add -A
$ git commit -m "Implement advanced login"
$ git checkout master
$ git merge advanced-login
$ git push

* The Github URL address for myapp2 project is: https://github.com/jimmy2046/myapp2

Completed Ch 9: Advanced Login

* I keep going forward to chapter 9 to create "remember me" function during user log in.

* As usual, I switching to a branch called "advanced-login".
$ git checkout -b advanced-login

* I added the remember_digest field of string in the Users data model.
$ rails generate migration add_remember_digest_to_users remember_digest:string

* This was the content of the database migration file 20170714053652_add_remember_digest_to_users.rb
sample_app/db/migrate/20170714053652_add_remember_digest_to_users.rb


class AddRememberDigestToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :remember_digest, :string
  end
end

* Then, I ran the database migration command.
$ rails db:migrate
== 20170714053652 AddRememberDigestToUsers: migrating =========================
-- add_column(:users, :remember_digest, :string)
   -> 0.0280s
== 20170714053652 AddRememberDigestToUsers: migrated (0.0282s) ================

* I added a new method in User model to return a random token.

* I added a remember method to the User model.
sample_app/app/models/users.rb

* I defined the new token and digest methods using self.
sample_app/app/models/users.rb
  # Returns the hash digest of the given string.
  def self.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end

  # Returns a random token.
  def self.new_token
    SecureRandom.urlsafe_base64
  end

* I added an authenticated? method to the User model.
sample_app/app/models/user.rb
  # Returns true if the given token matches the digest.
  def authenticated?(remember_token)
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end

* I added the "remember_user" method to the Sessions Controller
sample_app/app/controllers/sessions_controller.rb
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      remember user
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

* I added the "remember(user)" method in the Sessions Helper
sample_app/app/helpers/sessions_helper.rb
  # Remembers a user in a persistent session.
  def remember(user)
    user.remember
    cookies.permanent.signed[:user_id] = user.id
    cookies.permanent[:remember_token] = user.remember_token
  end

* I re-wrote the "current_user" method in the Sessions helper.
sample_app/app/helpers/sessions_helper.rb
  # Returns the user corresponding to the remember token cookie.
  def current_user
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id])
      user = User.find_by(id: user_id)
      if user && user.authenticated?(cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
  end

* I added forget method in the User model to forget a user.
sample_app/app/models/user.rb
  def forget
    update_attribute(:remember_digest, nil)
  end

* I added the forget method in Sessions helper. And I added a line "forget(current_user)" in the log_out method to forget a user when a user logs out.
sample_app/app/helpers/sessions_helper.rb

* I added a line "delete logout_path" in Users Login Integration test to simulate a user clicking logout in a second window.
sample_app/test/integration/users_login_test.rb

* I modified the destroy method in Sessions Controller to log out a user only only when the user has already logged in.
sample_app/app/controllers/sessions_controller.rb

* I added a unit test "authenticated?" to test with a nonexistent digest.
sample_app/test/models/user_test.rb
  test "authenticated? should return false for a user with nil digest" do
    assert_not @user.authenticated?('')
  end

* I added a line "return false if remember_digest.nil?" in the "authenticated?" method in the User model.
sample_app/app/models/user.rb
  # Returns true if the given token matches the digest.
  def authenticated?(remember_token)
    return false if remember_digest.nil?
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end

* I added the "Remember me" checkbox in the log in screen.
sample_app/app/views/sessions/new.html.erb
      <%= f.label :remember_me, class: "checkbox inline" do %>
        <%= f.check_box :remember_me %>
        <span>Remember me on this computer</span>
      <% end %>

* I defined the CSS style for the Checkbox and the session_remember_me.
sample_app/app/assets/stylesheets/custom.scss
/* forms */
.checkbox {
  margin-top: -10px;
  margin-bottom: 10px;
  span {
    margin-left: 20px;
    font-weight: normal;
  }
}

#session_remember_me {
  width: auto;
  margin-left: 0;
}


* I added a line "params[:session][:remember_me] == '1' ? remember(user) : forget(user)"  in the Sessions controller to remember a user login if he checks on the "Remember me" checkout.
sample_app/app/controllers/sessions_controller.rb
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      params[:session][:remember_me] == '1' ? remember(user) : forget(user)
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

* I added the "log_in_as(user)" method in Test Helper and a new class "ActionDispatch" to test the "Remember me" feature.
sample_app/test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'

class ActiveSupport::TestCase
  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
  fixtures :all
  include ApplicationHelper
   
  # Add more helper methods to be used by all tests here...
   
  # Returns true if a test user is logged in.
  def is_logged_in?
    !session[:user_id].nil?
  end

  # Log in as a particular user.
  def log_in_as(user)
    session[:user_id] = user.id
  end       
end

class ActionDispatch::IntegrationTest

  # Log in as a particular user.
  def log_in_as(user, password: 'password', remember_me: '1')
    post login_path, params: { session: { email: user.email,
                                          password: password,
                                          remember_me: remember_me } }
  end
end

* I wrote two test cases "login with remembering" and "login without remembering" in the Users Login Integration Test to test the "Remember me" functionality.
sample_app/test/integration/users_login_test.rb
  test "login with remembering" do
    log_in_as(@user, remember_me: '1')
    assert_not_empty cookies['remember_token']
  end

  test "login without remembering" do
    # Log in to set the cookie.
    log_in_as(@user, remember_me: '1')
    # Log in again and verify that the cookie is deleted.
    log_in_as(@user, remember_me: '0')
    assert_empty cookies['remember_token']
  end

* I added a line to raise an exception in the suspected untested block of code.
sample_app/app/helpers/sessions_helper.rb

* I created a new file "sessions_helper_test.rb" for testing of  persistent sessions.
sample_app/test/helpers/sessions_helper_test.rb
require 'test_helper'

class SessionsHelperTest < ActionView::TestCase

  def setup
    @user = users(:michael)
    remember(@user)
  end

  test "current_user returns right user when session is nil" do
    assert_equal @user, current_user
    assert is_logged_in?
  end

  test "current_user returns nil when remember digest is wrong" do
    @user.update_attribute(:remember_digest, User.digest(User.new_token))
    assert_nil current_user
  end
end


* I deleted the "raise exception" line in Sessions Helper.
sample_app/app/helpers/sessions_helper.rb

* Horay, I had completed chapter 9. Just like usual, I ran a test, added all untracked files, committed changes, merged back to Master branch, pushed on to Github.
$ rails test
$ git add -A
$ git commit -m "Implement advanced login"
$ git checkout master
$ git merge advanced-login
$ git push


* The Github repository address for Michael Hartl's sample_app is https://github.com/jimmy2046/sample_app.

* The screenshot of the login page with "Remember me" function.

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.


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 ...