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.

No comments:

Post a Comment

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