Commit 737ff3f9 by Mai Hoang Thai Ha

Merge branch 'updating-users' into 'master'

Updating users

See merge request !11
parents 78169ac8 62a1e377
Pipeline #1273 canceled with stages
in 0 seconds
...@@ -6,6 +6,9 @@ ruby '3.0.1' ...@@ -6,6 +6,9 @@ ruby '3.0.1'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main'
gem 'rails', '~> 6.1.3', '>= 6.1.3.2' gem 'rails', '~> 6.1.3', '>= 6.1.3.2'
gem 'bcrypt', '3.1.13' gem 'bcrypt', '3.1.13'
gem 'faker', '~> 2.18'
gem 'will_paginate', '~> 3.3.0'
gem 'bootstrap-will_paginate'
gem 'bootstrap-sass', '3.4.1' gem 'bootstrap-sass', '3.4.1'
gem 'autoprefixer-rails' gem 'autoprefixer-rails'
# Use Puma as the app server # Use Puma as the app server
......
...@@ -72,6 +72,8 @@ GEM ...@@ -72,6 +72,8 @@ GEM
bootstrap-sass (3.4.1) bootstrap-sass (3.4.1)
autoprefixer-rails (>= 5.2.1) autoprefixer-rails (>= 5.2.1)
sassc (>= 2.0.0) sassc (>= 2.0.0)
bootstrap-will_paginate (1.0.0)
will_paginate
builder (3.2.4) builder (3.2.4)
byebug (11.1.3) byebug (11.1.3)
capybara (3.35.3) capybara (3.35.3)
...@@ -84,10 +86,12 @@ GEM ...@@ -84,10 +86,12 @@ GEM
xpath (~> 3.2) xpath (~> 3.2)
childprocess (3.0.0) childprocess (3.0.0)
coderay (1.1.3) coderay (1.1.3)
concurrent-ruby (1.1.8) concurrent-ruby (1.1.9)
crass (1.0.6) crass (1.0.6)
erubi (1.10.0) erubi (1.10.0)
execjs (2.8.1) execjs (2.8.1)
faker (2.18.0)
i18n (>= 1.6, < 2)
ffi (1.15.1) ffi (1.15.1)
formatador (0.2.5) formatador (0.2.5)
globalid (0.4.2) globalid (0.4.2)
...@@ -112,7 +116,7 @@ GEM ...@@ -112,7 +116,7 @@ GEM
listen (3.5.1) listen (3.5.1)
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.9.1) loofah (2.10.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
lumberjack (1.2.8) lumberjack (1.2.8)
...@@ -130,7 +134,7 @@ GEM ...@@ -130,7 +134,7 @@ GEM
msgpack (1.4.2) msgpack (1.4.2)
nenv (0.3.0) nenv (0.3.0)
nio4r (2.5.7) nio4r (2.5.7)
nokogiri (1.11.6-x86_64-linux) nokogiri (1.11.7-x86_64-linux)
racc (~> 1.4) racc (~> 1.4)
notiffany (0.1.3) notiffany (0.1.3)
nenv (~> 0.1) nenv (~> 0.1)
...@@ -233,9 +237,10 @@ GEM ...@@ -233,9 +237,10 @@ GEM
rack-proxy (>= 0.6.1) rack-proxy (>= 0.6.1)
railties (>= 5.2) railties (>= 5.2)
semantic_range (>= 2.3.0) semantic_range (>= 2.3.0)
websocket-driver (0.7.4) websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
will_paginate (3.3.0)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.4.2) zeitwerk (2.4.2)
...@@ -248,8 +253,10 @@ DEPENDENCIES ...@@ -248,8 +253,10 @@ DEPENDENCIES
bcrypt (= 3.1.13) bcrypt (= 3.1.13)
bootsnap (>= 1.4.4) bootsnap (>= 1.4.4)
bootstrap-sass (= 3.4.1) bootstrap-sass (= 3.4.1)
bootstrap-will_paginate
byebug byebug
capybara (>= 3.26) capybara (>= 3.26)
faker (~> 2.18)
guard (= 2.16.2) guard (= 2.16.2)
guard-minitest (= 2.4.6) guard-minitest (= 2.4.6)
jbuilder (~> 2.7) jbuilder (~> 2.7)
...@@ -271,6 +278,7 @@ DEPENDENCIES ...@@ -271,6 +278,7 @@ DEPENDENCIES
web-console (>= 4.1.0) web-console (>= 4.1.0)
webdrivers webdrivers
webpacker (~> 5.0) webpacker (~> 5.0)
will_paginate (~> 3.3.0)
RUBY VERSION RUBY VERSION
ruby 3.0.1p64 ruby 3.0.1p64
......
...@@ -210,4 +210,16 @@ input { ...@@ -210,4 +210,16 @@ input {
} }
} }
} }
}
// Users index
.users {
list-style: none;
margin: 0;
li {
overflow: auto;
padding: 10px 0;
border-bottom: 1px solid $gray-medium-light;
}
} }
\ No newline at end of file
...@@ -7,7 +7,7 @@ class SessionsController < ApplicationController ...@@ -7,7 +7,7 @@ class SessionsController < ApplicationController
if user && user.authenticate(params[:session][:password]) if user && user.authenticate(params[:session][:password])
log_in user log_in user
params[:session][:remember_me] == '1'? remember(user) : forget(user) params[:session][:remember_me] == '1'? remember(user) : forget(user)
redirect_to user redirect_back_or user
else else
flash.now[:danger] = 'Invalid email/password combination' flash.now[:danger] = 'Invalid email/password combination'
render 'new' render 'new'
......
class UsersController < ApplicationController class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
def index
@users = User.paginate(page: params[:page])
end
def show def show
@user = User.find(params[:id]) @user = User.find(params[:id])
...@@ -20,10 +27,50 @@ class UsersController < ApplicationController ...@@ -20,10 +27,50 @@ class UsersController < ApplicationController
end end
end end
private def edit
@user = User.find(params[:id])
end
def update
@user = User.find(params[:id])
if @user.update(user_params)
flash[:success] = "Profile updated"
redirect_to @user
else
render 'edit'
end
end
def user_params def destroy
params.require(:user).permit(:name, :email, :password, User.find(params[:id]).destroy
:password_confirmation) flash[:success] = "User deleted"
redirect_to users_url
end end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# Before filters
# Confirm a logged_in user
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
# Confirms the correct user
def correct_user
@user = User.find(params[:id])
redirect_to(root_url) unless current_user?(@user)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end end
...@@ -16,7 +16,6 @@ module SessionsHelper ...@@ -16,7 +16,6 @@ module SessionsHelper
if (user_id = session[:user_id]) if (user_id = session[:user_id])
@current_user ||= User.find_by(id: user_id) @current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.encrypted[:user_id]) elsif (user_id = cookies.encrypted[:user_id])
raise # The tests still pass, so this branch is currently untested.
user = User.find_by(id: user_id) user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token]) if user && user.authenticated?(cookies[:remember_token])
log_in user log_in user
...@@ -25,6 +24,11 @@ module SessionsHelper ...@@ -25,6 +24,11 @@ module SessionsHelper
end end
end end
# Returns true if the given user is the current user
def current_user?(user)
user && user == current_user
end
# Return true if the user is loffed in, false otherwise # Return true if the user is loffed in, false otherwise
def logged_in? def logged_in?
!current_user.nil? !current_user.nil?
...@@ -40,5 +44,16 @@ module SessionsHelper ...@@ -40,5 +44,16 @@ module SessionsHelper
forget(current_user) forget(current_user)
session.delete(:user_id) session.delete(:user_id)
@current_user = nil @current_user = nil
end 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
# Stores the URL trying to be accessed
def store_location
session[:forwarding_url] = request.original_url if request.get?
end
end end
module UsersHelper module UsersHelper
# Return the Gravatar for the given user # Return the Gravatar for the given user
def gravatar_for(user) def gravatar_for(user, options = { size: 80 })
size = options[:size]
gravatar_id = Digest::MD5::hexdigest(user.email.downcase) gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}" gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
image_tag(gravatar_url, alt: user.name, class: "gravatar") image_tag(gravatar_url, alt: user.name, class: "gravatar")
end end
end end
...@@ -7,7 +7,7 @@ class User < ApplicationRecord ...@@ -7,7 +7,7 @@ class User < ApplicationRecord
format: {with: VALID_EMAIL_REGEX}, format: {with: VALID_EMAIL_REGEX},
uniqueness: true uniqueness: true
has_secure_password has_secure_password
validates :password, presence: true, length: {minimum: 6} validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
# Returns the hash digest of the given string # Returns the hash digest of the given string
def User.digest(string) def User.digest(string)
......
...@@ -20,14 +20,14 @@ ...@@ -20,14 +20,14 @@
<li><%= link_to "Home", root_path %></li> <li><%= link_to "Home", root_path %></li>
<li><%= link_to "Help", help_path %></li> <li><%= link_to "Help", help_path %></li>
<% if logged_in? %> <% if logged_in? %>
<li><%= link_to "Users", '#' %></li> <li><%= link_to "Users", users_path %></li>
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle='dropdown'> <a href="#" class="dropdown-toggle" data-toggle='dropdown'>
Account <b class="caret"></b> Account <b class="caret"></b>
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><%= link_to "Profile", current_user %></li> <li><%= link_to "Profile", current_user %></li>
<li><%= link_to "Settings", '#' %></li> <li><%= link_to "Settings", edit_user_path(current_user) %></li>
<li class="divider"></li> <li class="divider"></li>
<li> <li>
<%= link_to "Log out", logout_path, method: :delete %> <%= link_to "Log out", logout_path, method: :delete %>
......
<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>
\ No newline at end of file
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_with(model: @user, local: true) 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 %>
<%= 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="https://gravatar.com/emails" target="_blank">Change</a>
</div>
</div>
</div>
\ No newline at end of file
<%= provide(:title, 'All users') %>
<h1>All users</h1>
<%= will_paginate %>
<ul class="users">
<% @users.each do |user| %>
<%= render @users %>
<% end %>
</ul>
<%= will_paginate %>
\ No newline at end of file
...@@ -8,6 +8,7 @@ if !defined?(Spring) && [nil, "development", "test"].include?(ENV["RAILS_ENV"]) ...@@ -8,6 +8,7 @@ if !defined?(Spring) && [nil, "development", "test"].include?(ENV["RAILS_ENV"])
Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
gem "spring", spring.version gem "spring", spring.version
require "spring/binstub" require "spring/binstub"
rescue Gem::LoadError rescue Gem::LoadError
# Ignore when Spring is not installed. # Ignore when Spring is not installed.
end end
......
class AddAdminToUsers < ActiveRecord::Migration[6.1]
def change
add_column :users, :admin, :boolean, default: false
end
end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_06_11_030107) do ActiveRecord::Schema.define(version: 2021_06_15_072636) do
create_table "users", force: :cascade do |t| create_table "users", force: :cascade do |t|
t.string "name" t.string "name"
...@@ -19,6 +19,7 @@ ActiveRecord::Schema.define(version: 2021_06_11_030107) do ...@@ -19,6 +19,7 @@ ActiveRecord::Schema.define(version: 2021_06_11_030107) do
t.datetime "updated_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false
t.string "password_digest" t.string "password_digest"
t.string "remember_digest" t.string "remember_digest"
t.boolean "admin", default: false
t.index ["email"], name: "index_users_on_email", unique: true t.index ["email"], name: "index_users_on_email", unique: true
end end
......
# This file should contain all the record creation needed to seed the database with its default values. User.create!( name: "Example User",
# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). email: "example@railstutorial.org",
# password: "foobar",
# Examples: password_confirmation: "foobar",
# admin: true)
# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
# Character.create(name: 'Luke', movie: movies.first) 99.times do |num|
name = Faker::Name.name
email = "example-#{num+1}@railstutorials.org"
password = "password"
User.create!(name: name, email: email, password: password, password_confirmation: password)
end
\ No newline at end of file
require "test_helper" require "test_helper"
class UsersControllerTest < ActionDispatch::IntegrationTest class UsersControllerTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
@other_user = users(:archer)
end
test "should get new" do test "should get new" do
get signup_path get signup_path
assert_response :success assert_response :success
end end
end
test "should redirect index when not logged in" do
get users_path
assert_redirected_to login_url
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
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
test "should redirect edit when logged in as wrong user" do
log_in_as(@other_user)
get edit_user_path(@user)
assert flash.empty?
assert_redirected_to root_url
end
test "should redirect update when logged in as wrong user" do
log_in_as(@other_user)
patch user_path(@user), params: { user: { name: @user.name,email: @user.email } }
assert flash.empty?
assert_redirected_to root_url
end
end
\ No newline at end of file
michael: michael:
name: Michael Example name: Michael Example
email: michael@example.com email: michael@example.com
password_digest: <%= User.digest('password') %> password_digest: <%= User.digest('password') %>
\ No newline at end of file
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 %>
\ No newline at end of file
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 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
end
require "test_helper"
class UsersIndexTest < ActionDispatch::IntegrationTest
def setup
@admin = users(:michael)
@non_admin = users(:archer)
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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment