Monday, July 17, 2017

Completed Ch 11: Account activation

* I created a new branch called: account-activation
$ git checkout -b account-activation

* I generated an Account Activation controller.
$ rails generate controller AccountActivations

* I added a route for account activation.
config/routes.rb
  resources :account_activations, only: [:edit]

* In the Users data model, I added activation_digest, activated, and activated_at fields.
$ rails generate migration add_activation_to_users \
> activation_digest:string activated:boolean activated_at:datetime

* In the DB migration file, I added the default value = false for the activated field.
sample_app/db/migrate
class AddActivationToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :activation_digest, :string
    add_column :users, :activated, :boolean, default: false
    add_column :users, :activated_at, :datetime
  end
end

* Then, I ran DB migrate command.
$ rails db:migrate

* In the User model, I added account activation code.
sample_app/app/models/user.rb
class User < ApplicationRecord
  attr_accessor :remember_token, :activation_token
  before_save   :downcase_email
  before_create :create_activation_digest
  validates :name,  presence: true, length: { maximum: 50 }
  .
  .
  .
  private

    # Converts email to all lower-case.
    def downcase_email
      self.email = email.downcase
    end

    # Creates and assigns the activation token and digest.
    def create_activation_digest
      self.activation_token  = User.new_token
      self.activation_digest = User.digest(activation_token)
    end
end

* In DB Seeds file, I set the status the admin user and first 99 users to activated = true.
sample_app/db/seeds.rb
activated: true,
activated_at: Time.zone.now)

* I set the activated = true status to the test fixture file too.
sample_app/test/fixtures/users.yml
  activated: true
  activated_at: <%= Time.zone.now %>

* Then, I reset the DB and re-generated the seeds file.
$ rails db:migrate:reset
$ rails db:seed

* The next section was generating the mailer for account_activation and password_reset.
$ rails generate mailer UserMailer account_activation password_reset

* I updated the from address of Application Mailer.
sample_app/app/mailers/application_mailer.rb
  default from: "noreply@example.com"

* I updated the mailing template for User Mailer.
sample_app/app/mailers/user_mailer.rb
  def account_activation(user)
    @user = user
    mail to: user.email, subject: "Account activation"
  end

* I drafted the email template in Text file for accound activation.
sample_app/app/views/user_mailer/account_activation.text.erb
Hi <%= @user.name %>,

Welcome to the Sample App! Click on the link below to activate your account:

<%= edit_account_activation_url(@user.activation_token, email: @user.email) %>

* I did it for HTML version as well.
sample_app/app/views/user_mailer/account_activation.html.erb
<h1>Sample App</h1>

<p>Hi <%= @user.name %>,</p>

<p>
Welcome to the Sample App! Click on the link below to activate your account:
</p>

<%= link_to "Activate", edit_account_activation_url(@user.activation_token,
                                                    email: @user.email) %>


* I updated the config environment.
sample_app/config/environments/development.rb
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :test
  host = 'localhost:3000'
  config.action_mailer.default_url_options = { host: host, protocol: 'http' }
   
  config.action_mailer.perform_caching = false

* Then, I re-started my server.
Keyboard: Ctrl + C
$ rail s -b 0.0.0.0 -p 3000

* I updated Mailer preview file.
sample_app/test/mailers/previews/user_mailer_preview.rb

* I previewed the Mailer template. In Firefox, I typed: http://localhost:3000/rails/mailers/user_mailer/account_activation.html



* I copied the Account Activation test case for User Mailer.
sample_app/test/mailers/user_mailer_test.rb
  test "account_activation" do
    user = users(:michael)
    user.activation_token = User.new_token
    mail = UserMailer.account_activation(user)
    assert_equal "Account activation", mail.subject
    assert_equal [user.email], mail.to
    assert_equal ["noreply@example.com"], mail.from
    assert_match user.name,               mail.body.encoded
    assert_match user.activation_token,   mail.body.encoded
    assert_match CGI.escape(user.email),  mail.body.encoded
  end

* In Test Environment, I added the defaul_url_options to example.com
sample_app/config/environments/test.rb
  config.action_mailer.default_url_options = { host: 'example.com' }

* I editted the def create action of Users Controller.
sample_app/app/controllers/users_controller.rb
  def create
    @user = User.new(user_params)
    if @user.save
      UserMailer.account_activation(@user).deliver_now
      flash[:info] = "Please check your email to activate your account."
      redirect_to root_url
    else
      render 'new'
    end
  end

* I editted the User Signup Integration Test Suite.
test/integration/users_signup_test.rb
  test "invalid signup information" do
   assert_select 'div#error_explanation'
    assert_select 'div.field_with_errors'
  end

  test "valid signup information" do
    # assert_template 'users/show'
    # assert is_logged_in?
  end

* I added the def authenticated?(attribute, token) method in User model.
sample_app/app/models/user.rb
  # Returns true if the given token matches the digest.
  def authenticated?(attribute, token)
    digest = send("#{attribute}_digest")
    return false if digest.nil?
    BCrypt::Password.new(digest).is_password?(token)
  end

* I updated a line in Sessions Helper.
sample_app/app/helpers/sessions_helper.rb
      if user && user.authenticated?(:remember, cookies[:remember_token])

* I updated the test case "authenticated? should return false for a user with nil digest" in User Model Test Suite.
sample_app/test/models/user_test.rb
    assert_not @user.authenticated?(:remember, '')

* I added the edit action for Account Activation Controller.
sample_app/app/controllers/account_activations_controller.rb
class AccountActivationsController < ApplicationController

  def edit
    user = User.find_by(email: params[:email])
    if user && !user.activated? && user.authenticated?(:activation, params[:id])
      user.update_attribute(:activated,    true)
      user.update_attribute(:activated_at, Time.zone.now)
      log_in user
      flash[:success] = "Account activated!"
      redirect_to user
    else
      flash[:danger] = "Invalid activation link"
      redirect_to root_url
    end
  end   

end

* I pasted the following URL from the server log file to activate the new user. In Firefox, I pasted this URL:
http://localhost:3000/account_activations/UB_1LRUTcNuhOPiMvF6HvQ/edit?email=jimmyc%40example.com

* The activation was successful.


* I updated the create action in 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])
      if user.activated?
        log_in user
        params[:session][:remember_me] == '1' ? remember(user) : forget(user)
        redirect_back_or user
      else
        message  = "Account not activated. "
        message += "Check your email for the activation link."
        flash[:warning] = message
        redirect_to root_url
      end
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end


* I edited the User SignUp Integration Test.
sample_app/test/integration/users_signup_test.rb
require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest

  def setup
    ActionMailer::Base.deliveries.clear
  end
       
  test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      post users_path, params: { user: { name:  "",
                                         email: "user@invalid",
                                         password:              "foo",
                                         password_confirmation: "bar" } }
    end
    assert_template 'users/new'
    assert_select 'div#error_explanation'
    assert_select 'div.field_with_errors'     
  end   

  test "valid signup information with account activation" do
    get signup_path
    assert_difference 'User.count', 1 do
      post users_path, params: { user: { name:  "Example User",
                                         email: "user@example.com",
                                         password:              "password",
                                         password_confirmation: "password" } }
    end
    assert_equal 1, ActionMailer::Base.deliveries.size
    user = assigns(:user)
    assert_not user.activated?
    # Try to log in before activation.
    log_in_as(user)
    assert_not is_logged_in?
    # Invalid activation token
    get edit_account_activation_path("invalid token", email: user.email)
    assert_not is_logged_in?
    # Valid token, wrong email
    get edit_account_activation_path(user.activation_token, email: 'wrong')
    assert_not is_logged_in?
    # Valid activation token
    get edit_account_activation_path(user.activation_token, email: user.email)
    assert user.reload.activated?
    follow_redirect!
    assert_template 'users/show'
    assert is_logged_in?
  end
   
end

* I added activate and send_activation_email actions in User model.
sample_app/app/models/user.rb
  # Activates an account.
  def activate
    update_attribute(:activated,    true)
    update_attribute(:activated_at, Time.zone.now)
  end

  # Sends activation email.
  def send_activation_email
    UserMailer.account_activation(self).deliver_now
  end

* I updated the action name for sending activationg email in Users Controller.
sample_app/app/controllers/users_controller.rb
@user.send_activation_email

* I updated (refactored) the codes to activate a user.
sample/app/controllers/account_activations_controller.rb
user.activate

* I simplified the two transactions into one transaction in User model.
sample_app/app/models/user.rb
update_columns(activated: true, activated_at: Time.zone.now)

* I edited the index action and show action to show only active users in Users Controller.
  def index
    @users = User.where(activated: true).paginate(page: params[:page])     
  end

  def show
    @user = User.find(params[:id])
    redirect_to root_url and return unless @user.activated?     
  end

* At the end of the chapter 11, I skipped the email in production using Heroku part. I ran a test, added all untracked files, committed the changes, merged back to Master branch, and pushed onto Github.
$ rails test
$ git add -A
$ git commit -m "Add account activation"
$ git checkout master
$ git merge account-activation




* The Github address for Michael Hartl's sample app project was: https://github.com/jimmy2046/sample_app

Experiment: To Include Bootstrap Gem in My Ruby on Rails Project

* Currently, my Ruby on Rails project myapp2 did not equipped with the Bootstrap Gem.

* I created a new branch called: adding-bootstrap-attempt.
$ cd myapp2
$ git checkout -b adding-bootstrap-attempt

* I added the Bootstrap Gem into Gemfile.
myapp2/Gemfile
# Use Bookstrap
gem 'bootstrap-sass', '3.3.6'

* Then, I ran bundle install
$ bundle install
$ bundle show bootstrap-sass
/home/jimmyc/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/bootstrap-sass-3.3.6

* I created a new file called: custom.scss and copied the format for .user class
myapp2/app/assets/stylesheets/custom.scss
/* Users index */

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

* I tried to copy all codes in custom.scss in Michael Hartl's sample_app to myapp2.

* It seemed that I could not copy everything in custom.scss. So I had to copy selectively. So, I deleted the typography (h1, h2, ..., h6, and p)  section. Then, I delete the header section (#logo) as well as footer section.

* In, Application Layout file, I changed CSS file for the stylesheet_link_tag.
myapp2/app/views/layout/application.html.erb
<%= stylesheet_link_tag 'templatemo-style.css' %>

* I pre-defined the asset file in assets.rb
myapp2/config/initializers/assets/rb
Rails.application.config.assets.precompile += %w( templatemo-style.css )

* I re-loaded Firefox, but the page seemed a little bit weird.

* I added custom.scss before templatemo-style.css.
myapp2/app/views/layout/application.html.erb
<%= stylesheet_link_tag 'custom.scss' %>
<%= stylesheet_link_tag 'templatemo-style.css' %>

* After I had done some trial and errors, these were the latest version of the files that I edited.

* The Assets Initializers file.
myapp2/config/initailizers/assets.rb
Rails.application.config.assets.version = '1.0'
Rails.application.config.assets.precompile += %w( templatemo-style.css )
Rails.application.config.assets.precompile += %w( bootstrap.min.css )

*  The Application Layouts file
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' %>     

      <%= stylesheet_link_tag 'bootstrap.min.css',
                            media: 'all',
                            'data-turbolinks-track': 'reload' %>

      <%= stylesheet_link_tag 'templatemo-style.css',
                            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>

* The CSS stylesheet file.
myapp2/app/assets/stylesheets/tempaltemo-style.css
/*

Classic Template

http://www.templatemo.com/tm-488-classic

-----------------------------*/

body {
    color: #000000;
    font-family: 'Open Sans', Helvetica, Arial, sans-serif;
    font-size: 18px;
    font-weight: 300;
    overflow-x: hidden;
}

a, button { transition: all 0.3s ease; }
a:hover,
a:focus {
    text-decoration: none;
    outline: none;
}

h2 { font-size: 2.2rem; }
.tm-thin-font { font-weight: 300; }

.container-fluid {
    margin-left: auto;
    margin-right: auto;
    max-width: 1390px;
    overflow-x: hidden;
}

@media (max-width: 1390px) {
    .container-fluid {
        padding-left: 5%;
        padding-right: 5%;
    }
}

.tm-header-inner {
    display: -webkit-center;
    display: -ms-flexbox;
    display: flex;
    -webkit-align-items: top;
        -ms-flex-align: center;
            align-items: center;
    -webkit-justify-content: space-between;
        -ms-flex-pack: justify;
            justify-content: space-between;
    height: 175px;
}

.tm-site-name {
    color: #cc9900;
    display: block;
    font-size: 2.6rem;
    font-weight: 400;
}

.tm-main-nav { font-size: 1.2rem; }

.navbar-nav .nav-link {
    border-radius: 6px;
    color: black;
    padding: 10px 40px;
}

.nav-item.active .nav-link,
.nav-link:hover,
.nav-link:focus {
    color: white;
    background-color: #cc9900;

}

.tm-gold-text { color: #cc9900; }

.tm-section {
    padding-top: 80px;
    padding-bottom: 100px;
}

.tm-home-img-container {
/* old:    background-image: url('../img/tm-home-img.jpg'); */
    background-image: url('tm-home-img.jpg');
    background-size: auto;
    background-position: center;
    background-repeat: no-repeat;
    height: 500px;
}

.tm-about-img-container { background-image: url('tm-about-img.jpg');    }
.tm-blog-img-container { background-image: url('tm-blog-img.jpg'); }
.tm-contact-img-container { background-image: url('tm-contact-img.jpg'); }

.tm-about-img-container,
.tm-blog-img-container,
.tm-contact-img-container {
    background-size: auto 200px;
    background-position: center;
    background-repeat: no-repeat;
    height: 200px;
}

.tm-title {
    margin-bottom: 1rem;
    line-height: 1.4;
}

.tm-subtitle {
    font-size: 1.4rem;
    max-width: 800px;
    margin: 0 auto 80px;
}

.tm-content-box {
    max-width: 310px;
    margin: 0 auto;
}

h3 { font-size: 1.65rem; }
h4 {
    font-size: 1.4rem;
    line-height: 1.6;
}

.tm-btn {
    color: white;
    background-color: #cc9900;
    border: none;
    border-radius: 5px;
    display: inline-block;
    padding: 10px 30px;
}

.tm-btn:hover,
.tm-btn:focus {
    background-color: #906E09;
    color: white;
    outline: none;
}

.tm-btn-gray { background-color: #666666; }
.tm-btn-gray:hover,
.tm-btn-gray:focus {
    background-color: #515050;   
}

.tm-margin-b-15 { margin-bottom: 15px; }
.tm-margin-b-20 { margin-bottom: 20px; }
.tm-margin-b-30 { margin-bottom: 30px; }
.tm-margin-b-40 { margin-bottom: 40px; }
.tm-margin-t-big { margin-top: 90px; }
.tm-margin-t-mid { margin-top: 60px; }
.tm-margin-t-small { margin-top: 30px; }

.tm-text-link {
    color: #006699;
    line-height: 2.8;
    text-decoration: underline;
}
.tm-overflow-auto { overflow: auto; }
.tm-small-font { font-size: 1rem; }
.tm-related-post { margin-bottom: 40px; }
.tm-related-post:last-child { margin-bottom: 0; }
.media-left { padding-right: 25px; }
.media-body {
    border-bottom: 1px solid #ccc;
    padding-bottom: 25px;
}

.tm-media-description { margin-bottom: 0; }

.tm-2-col-right { padding-left: 20px; }

.tm-footer {
    color: #c6c6c6;
    background-color: #191919;
    background-image: url('../img/classic-pattern-bg.png');
    font-size: 1rem;
    padding-top: 40px;
    padding-bottom: 20px;
}

.tm-footer-links-container { padding-left: 10px; }

.tm-footer-link {
    color: #CCCC66;
    line-height: 2.8;
    text-decoration: underline;
}

.tm-footer-link:hover,
.tm-footer-link:focus {
    color: #CCCC66;
    text-decoration: none;
}
.tm-footer-thumbnail { margin-bottom: 5px; }

.tm-copyright-text {
    color: #999999;
    margin-bottom: 0;
}

hr { border-top: 1px solid #CCCCCC; }
p { line-height: 1.9; }

.tm-gray-bg {
    background-color: #CCCCCC;
    color: black;
    padding: 30px 20px 20px;
}
.tm-footer-content-box-title { margin-bottom: 30px; }

.tm-img-post { margin-bottom: 30px; }

.tm-aside-r { padding-left: 30px; }

.form-control {
    border-radius: 0;
    font-size: 1.1rem;
    padding: 0.75rem 1rem;
}

.form-control:focus { border-color: #CC9900; }

.tm-form-description { margin-top: 30px; }
.tm-contact-form { padding-top: 20px; }

#google-map {
    height: 333px;
    width: 100%;
    margin-top: 40px;
}

.tm-contact-right { padding-left: 30px; }
.tm-p-small { font-size: 1rem; }

@media (max-width: 1199px) {
    .tm-2-col-left { padding-right: 0; }
    .tm-2-col-right { padding-left: 0; }
    .tm-text-link {
        line-height: 2.2;
    }
    .container-fluid {
        padding-left: 4%;
        padding-right: 4%;
    }
}

@media (max-width: 991px){

    .tm-home-img-container {
        background:none;
        height: auto;
    }

    .tm-subtitle { margin-bottom: 40px; }
   
    #tmNavbar .navbar-nav .nav-link { padding: 10px 25px; }
    .media { max-width: 240px; }
    .media-left {
        display: block;
        margin-bottom: 20px;
    }

    .tm-content-box { margin-bottom: 50px; }
    .tm-text-link { line-height: 2.4; }

    .tm-section {
        padding-top: 50px;
        padding-bottom: 60px;
    }

    .tm-2-col-left, .tm-2-col-right {
        margin: 0 auto;
        max-width: 660px;
    }
   
    .tm-margin-t-big { margin-top: 30px; }
    .media { max-width: 100%; }
    .media-left {
        display: table-cell;
        margin-bottom: 0;
    }

    .tm-footer-content-box { margin-bottom: 40px; }

    .tm-2-rows-md-swap {
        display: -webkit-flex;
        display: -ms-flexbox;
        display: flex;
        -webkit-flex-direction: column;
            -ms-flex-direction: column;
                flex-direction: column;
    }

    .tm-2-rows-md-down-1 {
        -webkit-order: 1;
            -ms-flex-order: 1;
                order: 1;       
    }
    .tm-2-rows-md-down-2 {
        -webkit-order: 2;
            -ms-flex-order: 2;
                order: 2;
        margin-bottom: 0;
        margin-top: 30px;       
    }

    .tm-contact-right {
        padding-left: 15px;
        padding-top: 40px;
    }
}

@media (max-width: 897px) {
    .tm-btn {
        padding: 10px 15px;
        font-size: 1rem;
    }
}

@media (max-width: 767px) {
    .tm-main-nav {
        position: fixed;
        right: 0;
        top: 21px;
        z-index: 1000;
    }
    #tmNavbar .navbar-nav .nav-item { float: none; }
    #tmNavbar .navbar-nav .nav-link { padding: 10px 35px; }
    #tmNavbar {
        background: white;
        border-radius: 6px;
    }
    #tmNavbar .navbar-nav .nav-item+.nav-item { margin-left: 0; }
    .navbar-toggler {
        background: white;
        border-color: #cc9900;
        color: #cc9900;
        display: block;
        margin-left: auto;
        margin-right: 0;
    }
    .navbar-toggler:focus { outline: none; }
    .tm-content-box { margin-bottom: 50px; }
   
    .tm-2-col-left {
        padding-right: 0;
        max-width: 660px;
    }

    .tm-2-col-right { padding-left: 0; }
    .tm-margin-b-40 { margin-bottom: 25px; }
   
    .tm-copyright-text { padding-top: 0; }
    .tm-gray-bg { padding: 20px 20px 10px; }
    .tm-footer { padding-bottom: 15px; }

    .tm-2-rows-sm-swap {
        display: -webkit-flex;
        display: -ms-flexbox;
        display: flex;
        -webkit-flex-direction: column;
            -ms-flex-direction: column;
                flex-direction: column;
    }

    .tm-2-rows-sm-down-1 {
        -webkit-order: 1;
            -ms-flex-order: 1;
                order: 1;       
    }
    .tm-2-rows-sm-down-2 {
        -webkit-order: 2;
            -ms-flex-order: 2;
                order: 2;
        margin-bottom: 0;
        margin-top: 30px;       
    }

    .tm-sm-m-b { margin-bottom: 20px; }
    .tm-aside-r { padding-left: 15px; }
    .tm-aside-container {
        max-width: 310px;
        margin: 0 auto;
    }

    .tm-blog-post {    padding-bottom: 10px; }
    .tm-content-box-contact {
        margin-top: 40px;
        margin-bottom: 0;
    }

    .tm-contact-row-related-posts {    margin-top: 40px; }
}

@media (max-width: 543px) {
    .tm-footer-links-container { padding-left: 0; }
    .tm-xs-m-t { margin-top: 20px; }
    .tm-btn { padding: 10px 30px; }
}

@media(max-width: 510px) {
    .media { max-width: 240px; }
    .media-left {
        display: block;
        margin-bottom: 20px;
    }
       
    .tm-section {
        padding-top: 10%;
        padding-bottom: 10%;
    }

    .tm-subtitle { margin-bottom: 20px; }
    .tm-margin-b-40 { margin-bottom: 15px; }
    .tm-content-box { margin-bottom: 40px; }
    .tm-margin-t-mid { margin-top: 30px; }
    .tm-footer-content-box { margin-bottom: 40px; }
    .tm-footer-content-box-title { margin-bottom: 15px; }
    #google-map { margin-top: 25px; }
    .tm-map-section { margin-top: 50px; }

    .tm-contact-related-posts-container {
        max-width: 240px;
        margin-left: auto;
        margin-right: auto;
    }
}

* The Custom SCSS style file by Michael Hartl's
myapp2/app/assets/stylesheets/custom.scss
@import "bootstrap-sprockets";
@import "bootstrap";

/* mixins, variables, etc. */

$gray-medium-light: #eaeaea;

@mixin box_sizing {
  -moz-box-sizing:    border-box;
  -webkit-box-sizing: border-box;
  box-sizing:         border-box;
}

/* universal */

body {
  padding-top: 60px;
}

section {
  overflow: auto;
}

textarea {
  resize: vertical;
}

.center {
  text-align: center;
  h1 {
    margin-bottom: 10px;
  }
}

/* typography */

h1, h2, h3, h4, h5, h6 {
  line-height: 1;
}

h1 {
  font-size: 3em;
  letter-spacing: -2px;
  margin-bottom: 30px;
  text-align: center;
}

h2 {
  font-size: 1.2em;
  letter-spacing: -1px;
  margin-bottom: 30px;
  text-align: center;
  font-weight: normal;
  color: $gray-light;
}

p {
  font-size: 1.1em;
  line-height: 1.7em;
}


/* header */

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: white;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
  &:hover {
    color: white;
    text-decoration: none;
  }
}

/* footer */

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid $gray-medium-light;
  color: $gray-light;
  a {
    color: $gray;
    &:hover {
      color: $gray-darker;
    }
  }
  small {
    float: left;
  }
  ul {
    float: right;
    list-style: none;
    li {
      float: left;
      margin-left: 15px;
    }
  }
}

/* miscellaneous */

.debug_dump {
  clear: both;
  float: left;
  width: 100%;
  margin-top: 45px;
  @include box_sizing;
}

/* sidebar */

aside {
  section.user_info {
    margin-top: 20px;
  }
  section {
    padding: 10px 0;
    margin-top: 20px;
    &:first-child {
      border: 0;
      padding-top: 0;
    }
    span {
      display: block;
      margin-bottom: 3px;
      line-height: 1;
    }
    h1 {
      font-size: 1.4em;
      text-align: left;
      letter-spacing: -1px;
      margin-bottom: 3px;
      margin-top: 0px;
    }
  }
}

.gravatar {
  float: left;
  margin-right: 10px;
}

.gravatar_edit {
  margin-top: 15px;
}

/* forms */

input, textarea, select, .uneditable-input {
  border: 1px solid #bbb;
  width: 100%;
  margin-bottom: 15px;
  @include box_sizing;
}

input {
  height: auto !important;
}

#error_explanation {
  color: red;
  ul {
    color: red;
    margin: 0 0 30px 0;
  }
}

.field_with_errors {
  @extend .has-error;
  .form-control {
    color: $state-danger-text;
  }
}

.checkbox {
    margin-top: -10px;
    margin-bottom: 10px;
    span {
        margin-left: 20px;
        font-weight: normal;
    }
}

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


/* Users index */

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

* The Gemfile.
myapp2/Gemfile
source 'https://rubygems.org'

git_source(:github) do |repo_name|
  repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
  "https://github.com/#{repo_name}.git"
end


# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.0.2'

# Use the minitest ver 5.10.1 for testing stability
gem "minitest", "5.10.1"

# bcrypt for password digest
gem 'bcrypt',         '3.1.11'

# faker for creating sample users for testing
gem 'faker',          '1.7.3'

# For pagination
gem 'will_paginate',           '3.1.5'
gem 'bootstrap-will_paginate', '1.0.0'

# Use Bookstrap
gem 'bootstrap-sass', '3.3.6'

# gem for controller testing
gem 'rails-controller-testing'

# Use sqlite3 as the database for Active Record
gem 'sqlite3'
# Use Puma as the app server
gem 'puma', '~> 3.0'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# Use CoffeeScript for .coffee assets and views
gem 'coffee-rails', '~> 4.2'
# See https://github.com/rails/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby

# Use jquery as the JavaScript library
gem 'jquery-rails'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 3.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platform: :mri
end

group :development do
  # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '~> 3.0.5'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

* And this was the screenshot of the improved version myapp2 with required SCSS and CSS stylesheet files. It was not perfect and it still needed some fine tuning. But, it is good enough for me to do the Ruby on Rails coding.

* Finally, I ran the test to make sure no failures.
$ rails test
Running via Spring preloader in process 4347
Run options: --seed 18815

# Running:

/home/jimmyc/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/will_paginate-3.1.5/lib/will_paginate/view_helpers/link_renderer.rb:27: warning: constant ::Fixnum is deprecated
/home/jimmyc/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/will_paginate-3.1.5/lib/will_paginate/view_helpers/link_renderer.rb:91: warning: constant ::Fixnum is deprecated
./home/jimmyc/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/will_paginate-3.1.5/lib/will_paginate/view_helpers/link_renderer.rb:27: warning: constant ::Fixnum is deprecated
/home/jimmyc/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/will_paginate-3.1.5/lib/will_paginate/view_helpers/link_renderer.rb:91: warning: constant ::Fixnum is deprecated
...........................................

Finished in 2.413957s, 18.2273 runs/s, 81.1945 assertions/s.

44 runs, 196 assertions, 0 failures, 0 errors, 0 skips

* And then, I added all untracked files, committed the changes, merged back to Master branch, and pushed it onto Github.
$ git add -A
$ git commit -m "Improved SCSS file for All User Listing"
$ git checkout master
$ git merge adding-bootstrap-attempt
$ git push

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

* At this time, I planned to continue to Ch 11 of Michael Hartl's book using his original sample_app project.

Sunday, July 16, 2017

Chapter 10 In My Own Style

* First, I started the web server instance in myapp2
$ cd myapp2
$ rails s -b 0.0.0.0 -p 3000

* In Firefox, I typed: http://localhost:3000/

* I started Brackets and I changed the project folder to myapp2.

* I went to Michael Hartl's Ruby on Rails tutorial. https://www.railstutorial.org/book

* I went to Chapter 10 Updating, showing, and deleting users

* I created a branch called updating-users
$ git checkout -b updating-users

* I changed the heading of the <h2> tag to "Welcome to the myapp2".
myapp2/app/views/static_pages/home.html.erb
<h2 class="tm-gold-text tm-title">Welcome to the myapp2</h2>

* I changed the header of the website to myapp2.
myapp2/app/views/layouts/_header.html.erb
<%= link_to("myapp2", root_path, :class => "navbar-brand tm-site-name") %>

* I copied and pasted the codes in User Controllers and then I made some minor changes to split the name field into first_name and last_name.
myapp2/app/controllers/users_controller.rb 
    def user_params
      params.require(:user).permit(:first_name, :last_name, :email, :password,
                                   :password_confirmation)
    end

* I created an Edit view file for editing user profile. I copied and pasted the codes and split the name field into first_name and last_name.
myapp2/app/views/users/edit.html.erb
      <%= f.label :first_name %>
      <%= f.text_field :first_name, class: 'form-control' %>     

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

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

* I generated the Integration Test Suite to test User Edit function.
$ rails generate integration_test users_edit
Running via Spring preloader in process 5208
      invoke  test_unit
      create    test/integration/users_edit_test.rb

* I copied and pasted the codes in User Edit Integration Test Suite and I separated the name field into first_name and last_name.
test/integration/users_edit_test.rb
require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:jimmy)
  end

  test "unsuccessful edit" do
    log_in_as(@user)
    get edit_user_path(@user)
    assert_template 'users/edit'
    patch user_path(@user), params: { user: { first_name:  "",
                                              last_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'
    firstName = "Foo"
    lastName = "Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { first_name:  firstName,
                                              last_name:  lastName,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal firtName, @user.first_name
    assert_equal lastName, @user.last_name
    assert_equal email, @user.email
  end   

  test "successful edit with friendly forwarding" do
    get edit_user_path(@user)
    log_in_as(@user)
    assert_redirected_to edit_user_url(@user)
    firstName = "Foo"
    lastName = "Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { first_name:  firstName,
                                              last_name: lastName,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal firstName, @user.first_name
    assert_equal lastName, @user.last_name
    assert_equal email, @user.email
  end   
   
end

* I edited User Model to allow the allow_nil: true option to validates.
myapp2/app/models/user.rb
validates :password, presence: true, length: { minimum: 6 },  allow_nil: true

* I copied and pasted the codes in User Controller Test Suite and separated the name field into first_name and last_name.
myapp2/test/controllers/users_controller_test.rb
  test "should redirect update when not logged in" do
    patch user_path(@user), params: { user: { first_name: @user.first_name,
                                              last_name: @user.last_name,
                                              email: @user.email } }
    assert_not flash.empty?
    assert_redirected_to login_url
  end

* I copied and pasted the Test Fixture file and I separated the name field into first_name and last_name
myapp2/test/fixtures/users.yml
jimmy:
  first_name: Jimmy
  last_name: Chong
  email: jimmyc5@example.com
  password_digest: <%= User.digest('password') %>
 
michael:
  first_name: Michael
  last_name: Example
  email: michael@example.com
  password_digest: <%= User.digest('password') %>
  admin: true

archer:
  first_name: Sterling
  last_name: Archer
  email: duchess@example.com
  password_digest: <%= User.digest('password') %>

lana:
  first_name: Lana
  last_name: Kane
  email: hands@example.com
  password_digest: <%= User.digest('password') %>

malory:
  first_name: Malory
  last_name: Archer
  email: boss@example.com
  password_digest: <%= User.digest('password') %>

 <% 30.times do |n| %>
 user_<%= n %>:
  first_name:  <%= "User.first_name #{n}" %>
  last_name: <%= "User.last_name #{n}" %>
  email: <%= "user-#{n}@example.com" %>
  password_digest: <%= User.digest('password') %>
 <% end %>

* In the Session Helper class, I copied the def current_user?(user) and def redirect_back_or(default) actions.
myapp2/app/helpers/sessions_helper.rb
  # Returns true if the given user is the current user.
  def current_user?(user)
    user == current_user
  end

  # Redirects to stored location (or to the default).
  def redirect_back_or(default)
    redirect_to(session[:forwarding_url] || default)
    session.delete(:forwarding_url)
  end

* One more action called def store_location was copied to Session Helper
myapp2/app/helpers/sessions_helper.rb
  # Stores the URL trying to be accessed.
  def store_location
    session[:forwarding_url] = request.original_url if request.get?
  end

* In Session Controller, I updated the action to redirect_back_or user for friendly forwarding.
myapp2/app/controllers/sessions_controller.rb
redirect_back_or user

* I created and copied the codes in User Index file index.html.erb for listing out all users.
myapp2/app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>

<%= will_paginate %>

<ul class="users">
  <%= render @users %>
</ul>

<%= will_paginate %>

* I copied and pasted the codes in users_helper.rb for Gravatars.
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.first_name, class: "gravatar")
  end   
   
end

* Also, I defined the formatting and style for the .user class when listing out all users.
myapp2/app/assets/stylesheets/templatemo-style.css
/* Users index */

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

* I assigned the user_path for the Users menu in the Header.
myapp2/app/views/layouts/_header.html.erb
<li class="nav-item"><%= link_to "Users", users_path, class: "nav-link" %></li>

* I added 'faker', 'will_paginate', and 'bootstrap-will_paginate' in the Gemfile.
myapp2/Gemfile
# faker for creating sample users for testing
gem 'faker',          '1.7.3'

# For pagination
gem 'will_paginate',           '3.1.5'
gem 'bootstrap-will_paginate', '1.0.0'

* Then, I ran bundle install.
$ bundle install

* I copied and pasted a program for seeding the database with sample users. I also separated and first_name and last_name field.
myapp2/db/seeds.rb
User.create!(first_name:  "Example",
             last_name: "User",
             email: "example@railstutorial.org",
             password:              "foobar",
             password_confirmation: "foobar")

99.times do |n|
  firstName = Faker::Name.first_name
  lastName = Faker::Name.last_name
  email = "example-#{n+1}@railstutorial.org"
  password = "password"
  User.create!(first_name:  firstName,
               last_name: lastName,
               email: email,
               password:              password,
               password_confirmation: password)
end

* After I had saved the seeds.rb file, I reset the database and then invoked the Rake task using db:seed
$ rails db:migrate:reset
$ rails db:seed

* I created a partial file to render a user.
myapp2/app/views/users/_user.html.erb
<li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.name, user %>
</li>


* After I had run bundle install, I found Gem Faker was working, but Gem paginate wasn't. So, I re-started the Rails server.
Keyboard: Ctrl + C
$ rails s -b 0.0.0.0 -p 3000

* In the User View Partial file, I forgot to separate the first_name and last_name fields. I re-wrote it to match my data model.
myapp2/app/views/users/_user.html.erb
<li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.first_name, user %>
</li>

* I generated an Integration Test for user_index to test users are listed out properly.
$ rails generate integration_test users_index
Running via Spring preloader in process 7001
      invoke  test_unit
      create    test/integration/users_index_test.rb

* I copied and pasted the code in User Index Integration Test Suite. As of the time being, I only verified the first_name field.
myapp2/test/integration/users_index_test.rb
require 'test_helper'

class UsersIndexTest < ActionDispatch::IntegrationTest

  def setup
    @admin     = users(:michael)
    @non_admin = users(:archer)     
  end

  test "index including pagination" do
    log_in_as(@non_admin)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination', count: 2
    User.paginate(page: 1).each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.first_name
    end
  end   

  test "index as admin including pagination and delete links" do
    log_in_as(@admin)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination'
    first_page_of_users = User.paginate(page: 1)
    first_page_of_users.each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.first_name
      unless user == @admin
        assert_select 'a[href=?]', user_path(user), text: 'delete'
      end
    end
    assert_difference 'User.count', -1 do
      delete user_path(@non_admin)
    end
  end

  test "index as non-admin" do
    log_in_as(@non_admin)
    get users_path
    assert_select 'a', text: 'delete', count: 0
  end   
   
end

* I added the admin column for the User model to specify a user with admin privilege.
$ rails generate migration add_admin_to_users admin:boolean
Running via Spring preloader in process 7098
      invoke  active_record
      create    db/migrate/20170717004219_add_admin_to_users.rb

* I added default: false for the admin column.
myapp2/db/migrate
class AddAdminToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :admin, :boolean, default: false
  end
end

* Then, I ran DB migrate.
$ rails db:migrate
== 20170717004219 AddAdminToUsers: migrating ==================================
-- add_column(:users, :admin, :boolean, {:default=>false})
   -> 0.0056s
== 20170717004219 AddAdminToUsers: migrated (0.0064s) =========================

* I added admin: true for the Example User.
myapp2/db/seeds.rb
User.create!(first_name:  "Example",
             last_name: "User",
             email: "example@railstutorial.org",
             password:              "foobar",
             password_confirmation: "foobar",
             admin: true)

* Then, I reset the database and re-did the database seeding.
$ rails db:migrate:reset
$ rails db:seed

* Then, I added the delete links in the user views that is only accessible by admin.
myapp2/app/views/users/_user.html.erb
<li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.first_name, user %>
  <% if current_user.admin? && !current_user?(user) %>
    | <%= link_to "delete", user, method: :delete,
                                  data: { confirm: "You sure?" } %>
  <% end %>   
</li>

* When I ran a test, there were error of
Error:
UsersIndexTest#test_index_including_pagination:
ActionView::Template::Error: Undefined variable: "$gray-lighter".
    app/views/layouts/application.html.erb:6:in `_app_views_layouts_application_html_erb__254424222595337930_47226138909680'
    test/integration/users_index_test.rb:12:in `block in <class:UsersIndexTest>'

* Later, I found it Undefined variable: "$gray-lighter" is in the CSS style.
app/assets/stytlesheets/templatemo-style.css
.users {
  list-style: none;
  margin: 0;
  li {
    overflow: auto;
    padding: 10px 0;
/*    border-bottom: 1px solid $gray-lighter; */
      border-bottom: 1px solid;
  }

* There were 2 failures that 'div.pagination' could not be found.
$ rails test
Running via Spring preloader in process 7821
Run options: --seed 37045

# Running:

........F

Failure:
UsersIndexTest#test_index_as_admin_including_pagination_and_delete_links [/home/jimmyc/myapp2/test/integration/users_index_test.rb:24]:
Expected at least 1 element matching "div.pagination", found 0..
Expected 0 to be >= 1.


bin/rails test test/integration/users_index_test.rb:20

F

Failure:
UsersIndexTest#test_index_including_pagination [/home/jimmyc/myapp2/test/integration/users_index_test.rb:14]:
Expected exactly 2 elements matching "div.pagination", found 0..
Expected: 2
  Actual: 0


bin/rails test test/integration/users_index_test.rb:10

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

Finished in 2.308248s, 19.0621 runs/s, 45.9223 assertions/s.

44 runs, 106 assertions, 2 failures, 0 errors, 0 skips

* As I check the HTML file (in Firefox -> Ctrl + U), I saw that there were some odd with the pagination. One is
<div class="pagination"><ul class="pagination">

* Then, I started the server instance of Michael Hartl's original sample_app. I found that the patterns were just the same...
<div class="pagination"><ul class="pagination">

* I made some editing and this was the final version of the fixture file for Users.yml
myapp2/test/fixtures/users.yml
malory:
  first_name: Malory
  last_name: Archer
  email: boss@example.com
  password_digest: <%= User.digest('password') %>

<% 30.times do |n| %>
user_<%= n %>:
  first_name:  <%= "User #{n}" %>
  last_name:  <%= "User #{n}" %> 
  email: <%= "user-#{n}@example.com" %>
  password_digest: <%= User.digest('password') %>
<% end %>

* In _user.html.erb partial file, I concatenated the first_name and last_name of the user.
myapp2/app/views/users/_user.html.erb
<%= link_to user.first_name + " " + user.last_name, user %>

* In User Index Integration Test Suite, I changed the test case to verify a full name that includes first_name and last_name.
myapp2/test/integration/users_index_test.rb
assert_select 'a[href=?]', user_path(user), text: user.first_name + " " + user.last_name


* After all, I ran the test to make sure no failures.
$ rails test
Running via Spring preloader in process 9902
Run options: --seed 41776

# Running:

.................................../home/jimmyc/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/will_paginate-3.1.5/lib/will_paginate/view_helpers/link_renderer.rb:27: warning: constant ::Fixnum is deprecated
/home/jimmyc/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/will_paginate-3.1.5/lib/will_paginate/view_helpers/link_renderer.rb:91: warning: constant ::Fixnum is deprecated
.........

Finished in 2.264808s, 19.4277 runs/s, 85.6585 assertions/s.

44 runs, 194 assertions, 0 failures, 0 errors, 0 skips

* I added all untracked files.
$ git add -A

* I committed the changes and marked it an bootstrap had not been installed.
$ git commit -m "No Boostrap Finish user edit, update, index, and destroy actions"

* I merged it back into Master branch.
$ git checkout master
$ git merge updating-users

* Lastly, I pushed it onto Github (https://github.com/jimmy2046/myapp2).
$ git push 

Saturday, July 15, 2017

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

* After I could list out all users in the database, I added the 'faker' verion'1.7.3' gem in the Gemfile to create sample users for testing purpose.
sample_app/Gemfile
# faker for creating sample users for testing
gem 'faker',          '1.7.3'

* Then I ran bundle install.
$ bundle install
Bundle complete! 19 Gemfile dependencies, 67 gems now installed.
Gems in the group production were not installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.

$ bundle show faker
/home/jimmyc/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/faker-1.7.3

* I added a Ruby program to seed the database.
sample_app/db/seeds.rb
User.create!(name:  "Example User",
             email: "example@railstutorial.org",
             password:              "foobar",
             password_confirmation: "foobar")

99.times do |n|
  name  = Faker::Name.name
  email = "example-#{n+1}@railstutorial.org"
  password = "password"
  User.create!(name:  name,
               email: email,
               password:              password,
               password_confirmation: password)
end

* Next, I reset the database and then invoked the Rake task using db:seed
$ rails db:migrate:reset
$ rails db:seed

* I viewed the sample 100 users in the database. In Firefox, I type http://localhost:3000/users




* To limit only 30 users in a list user page, I added 'will_paginate' and 'bootstrap-will_paginate' Gems.
sample_app/Gemfile
# For pagination
gem 'will_paginate',           '3.1.5'
gem 'bootstrap-will_paginate', '1.0.0'

* Then, I ran bundle install.
$ bundle install
$ bundle show will_paginate
/home/jimmyc/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/will_paginate-3.1.5
$ bundle show bootstrap-will_paginate
/home/jimmyc/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/bootstrap-will_paginate-1.0.0

* I editted the User List View index.html.erb file to make it supports will_paginate
sample_app/app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>

<%= will_paginate %>

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

<%= will_paginate %>

* I re-wrote the index action of Users Controller to make it supports paginate function.
sample_app/app/controllers/users_controller.rb
  def index
    @users = User.paginate(page: params[:page])     
  end

* Then, I restarted the Rails Server.
Keyboard: Ctrl + C

  Rendered /home/jimmyc/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.0.2/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (1.2ms)
  Rendered /home/jimmyc/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.0.2/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout (29.1ms)
^C[3082] - Gracefully shutting down workers...
[3082] === puma shutdown: 2017-07-15 16:49:33 -0700 ===
[3082] - Goodbye!
Exiting
jimmyc@Jimmy-C-2017:~/sample_app$
$ rails s -b 0.0.0.0 -p 3000

* In Firefox, I type http://localhost:3000/. Then, "Users" menu on the top right corner.


* Now, this is important. I had to test whether the pagination works properly. The test included  to visit the index path, verify the first page of users is present, and then confirm that pagination is present on the page.

* I created a list of fixtures of 34 users for testing.
sample_app/test/fixtures/users.yml
michael:
  name: Michael Example
  email: michael@example.com
  password_digest: <%= User.digest('password') %>
 
archer:
  name: Sterling Archer
  email: duchess@example.gov
  password_digest: <%= User.digest('password') %>

lana:
  name: Lana Kane
  email: hands@example.gov
  password_digest: <%= User.digest('password') %>

malory:
  name: Malory Archer
  email: boss@example.gov
  password_digest: <%= User.digest('password') %>

<% 30.times do |n| %>
user_<%= n %>:
  name:  <%= "User #{n}" %>
  email: <%= "user-#{n}@example.com" %>
  password_digest: <%= User.digest('password') %>
<% end %>

* I generated an Integration Test to test User Index.
$ rails generate integration_test users_index
Running via Spring preloader in process 6254
      invoke  test_unit
      create    test/integration/users_index_test.rb

* I copied and pasted the codes for the User Index Integration Test Suite to verify that all User Index links are working properly.
sample_app/test/integration/users_index_test.rb
require 'test_helper'

class UsersIndexTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "index including pagination" do
    log_in_as(@user)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination', count: 2
    User.paginate(page: 1).each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.name
    end
  end   
   
end

* And then, I re-factored the User part of the User Index View.
sample_app/app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>

<%= will_paginate %>

<ul class="users">
  <% @users.each do |user| %>
    <%= render user %>       
  <% end %>
</ul>

<%= will_paginate %>

* I created a partial html.erb file _user.html.erb to display a single user in the user list.
sample_app/app/views/users/_user.html.erb
<li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.name, user %>
</li>

* Then, I simplied the codes in User Index
sample_app/app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>

<%= will_paginate %>

<ul class="users">
  <%= render @users %>
</ul>

<%= will_paginate %>

* After the ability of list users, I prepared to add the delete action for administrative user.

* First, I added the admin datafield attribute in the database metadata.
$ rails generate migration add_admin_to_users admin:boolean

* In the DB migration file, I added the default: false parameter to the admin field. That means a default new user added will be normal user, NOT admin user.
sample_app/db/migrate/20170716002656_add_admin_to_users.rb
class AddAdminToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :admin, :boolean, default: false
  end
end

* Then, I ran DB migrate command.
$ rails db:migrate

* After DB migration, I editted the seed file to make the first user is the admin user.
sample_app/db/seeds.rb
User.create!(name:  "Example User",
             email: "example@railstutorial.org",
             password:              "foobar",
             password_confirmation: "foobar",
             admin: true)

* And then, I reset the database.
$ rails db:migrate:reset
$ rails db:seed

* I added a test case "should not allow the admin attribute to be edited via the web" in the User Controller Test Suite.
sample_app/test/controllers/users_controller_test.rb
  test "should not allow the admin attribute to be edited via the web" do
    log_in_as(@other_user)
    assert_not @other_user.admin?
    patch user_path(@other_user), params: {
                                    user: { password:              "password",
                                            password_confirmation: "password",
                                            admin: true } }
    assert_not @other_user.admin?
  end

* In the _user.html.erb User Listing Partial file, I added a link to access the Delete User method that is granted to administrative privilege user.
sample_app/app/views/users/_user.html.erb
<li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.name, user %>
  <% if current_user.admin? && !current_user?(user) %>
    | <%= link_to "delete", user, method: :delete,
                                  data: { confirm: "You sure?" } %>
  <% end %>   
</li>

* In user controller, I added a destroy method in the logged_in_user before_action. In other words, a logged_in used can delete a record. And I added the def destroy method as well.
sample_app/app/controllers/users_controller.rb
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
  def destroy
    User.find(params[:id]).destroy
    flash[:success] = "User deleted"
    redirect_to users_url
  end

* I added one more line to allow only admin_user can do the destroy action. And I added the private def admin_user method for the confirmation of admin user.
sample_app/app/controllers/users_controller.rb
  before_action :admin_user,     only: :destroy
    # Confirms an admin user.
    def admin_user
      redirect_to(root_url) unless current_user.admin?
    end

  • Afterward, I tried to log in as admin user and deleted some users.
  • In Firefox, I typed: http://localhost:3000/
  • On the top right corner, I clicked Log in.
  • I input the Example User with admin privillege:
  • email: example@railstutorial.org
  • password: foobar
  • Then, I clicked the Log in button.
  • After log in, Ruby on Rails brought me to the Profile screen of the Admin user.
  • Then, I click User on the top menu.
  • Firefox URL showed http://localhost:3000/users. Then I saw all users had the delete link except for Example User (self).
 
  • I tried to delete the first user Titus Goldner. I clicked the delete button next to the name.
  • A message box You sure? with Cancel and OK buttons, popped up.
  • I clicked the OK button.
  • Titus Goldner was deleted. A "User deleted" Flash message appeared on the top.
  • I clicked the delete button for Flossie Macejkovic.
  • Then, I clicked cancel.
  • Flossie Macejkovic was still there.
  • I clicked the delete button for Ignatius Abernathy.
  • Then, I clicked OK.
  • Ignatius Abernathy is deleted. A "User deleted" Flash message appeared on the top.

* As I checked on the server log of Ruby on Rails server. When I am deleting a user, the following log message will show out.
Started DELETE "/users/5" for 127.0.0.1 at 2017-07-15 18:09:50 -0700
Processing by UsersController#destroy as HTML
  Parameters: {"authenticity_token"=>"v7doHoMVu55AZVNgwM3+SZvb2LVugO2yzqGDMl7hSYMmStu5LqFNmMiLhOLqmUT61q8T4k+IM7F0JhhAeZSTOQ==", "id"=>"5"}
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 5], ["LIMIT", 1]]
   (0.1ms)  begin transaction
  SQL (0.4ms)  DELETE FROM "users" WHERE "users"."id" = ?  [["id", 5]]
   (92.0ms)  commit transaction
Redirected to http://localhost:3000/users
Completed 302 Found in 98ms (ActiveRecord: 92.8ms)

* For the testing suite of the admin privilege. I added the admin attribute for fictional test user Michael in the Test Fixtures.
sample_app/test/fixtures/users.yml
michael:
  name: Michael Example
  email: michael@example.com
  password_digest: <%= User.digest('password') %>
  admin: true

* I added two test cases: "should redirect destroy when not logged in" and "should redirect destroy when logged in as a non-admin" in the Users Controller Test Suite.
sample_app/test/controllers/users_controller_test.rb
  test "should redirect destroy when not logged in" do
    assert_no_difference 'User.count' do
      delete user_path(@user)
    end
    assert_redirected_to login_url
  end

  test "should redirect destroy when logged in as a non-admin" do
    log_in_as(@other_user)
    assert_no_difference 'User.count' do
      delete user_path(@user)
    end
    assert_redirected_to root_url
  end

* On the other hand, I added two test cases "index as admin including pagination and delete links" and "index as non-admin" for User Index Integration Test.
sample_app/test/integration/users_index_test.rb
require 'test_helper'

class UsersIndexTest < ActionDispatch::IntegrationTest

  def setup
    @admin     = users(:michael)
    @non_admin = users(:archer)     
  end

  test "index including pagination" do
    log_in_as(@non_admin)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination', count: 2
    User.paginate(page: 1).each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.name
    end
  end   

  test "index as admin including pagination and delete links" do
    log_in_as(@admin)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination'
    first_page_of_users = User.paginate(page: 1)
    first_page_of_users.each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.name
      unless user == @admin
        assert_select 'a[href=?]', user_path(user), text: 'delete'
      end
    end
    assert_difference 'User.count', -1 do
      delete user_path(@non_admin)
    end
  end

  test "index as non-admin" do
    log_in_as(@non_admin)
    get users_path
    assert_select 'a', text: 'delete', count: 0
  end   
   
end

* Chapter 10 was a bit longer than others. To wrap up, I added all untracked files, committed the changes, merged to Master branch and pushed it onto Github. Then, I could call it a day.


$ git add -A
$ git commit -m "Finish user edit, update, index, and destroy actions"
$ git checkout master
$ git merge updating-users
$ git push 

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

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