Commit 8d649229 by Tô Ngọc Ánh

Merge branch 'updating-users' into 'master'

Finish user edit, update, index, and destroy actions

See merge request !8
parents 49d2b018 e3f8bd4e
Pipeline #675 failed with stages
in 0 seconds
...@@ -25,6 +25,11 @@ gem 'sdoc', '~> 0.4.0', group: :doc ...@@ -25,6 +25,11 @@ gem 'sdoc', '~> 0.4.0', group: :doc
# Use ActiveModel has_secure_password # Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7' gem 'bcrypt', '~> 3.1.7'
gem 'faker', '1.9.1'
gem 'will_paginate', '3.0.4'
gem 'bootstrap-will_paginate', '0.0.9'
# Use Unicorn as the app server # Use Unicorn as the app server
# gem 'unicorn' # gem 'unicorn'
......
...@@ -41,6 +41,8 @@ GEM ...@@ -41,6 +41,8 @@ GEM
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
bootstrap-sass (2.3.2.0) bootstrap-sass (2.3.2.0)
sass (~> 3.2) sass (~> 3.2)
bootstrap-will_paginate (0.0.9)
will_paginate
builder (3.2.4) builder (3.2.4)
byebug (11.1.3) byebug (11.1.3)
capybara (2.1.0) capybara (2.1.0)
...@@ -68,6 +70,8 @@ GEM ...@@ -68,6 +70,8 @@ GEM
factory_girl_rails (4.2.0) factory_girl_rails (4.2.0)
factory_girl (~> 4.2.0) factory_girl (~> 4.2.0)
railties (>= 3.0.0) railties (>= 3.0.0)
faker (1.9.1)
i18n (>= 0.7)
ffi (1.13.1) ffi (1.13.1)
globalid (0.4.2) globalid (0.4.2)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
...@@ -195,6 +199,7 @@ GEM ...@@ -195,6 +199,7 @@ GEM
railties (>= 4.0) railties (>= 4.0)
sprockets-rails (>= 2.0, < 4.0) sprockets-rails (>= 2.0, < 4.0)
websocket (1.0.7) websocket (1.0.7)
will_paginate (3.0.4)
xpath (2.1.0) xpath (2.1.0)
nokogiri (~> 1.3) nokogiri (~> 1.3)
...@@ -204,10 +209,12 @@ PLATFORMS ...@@ -204,10 +209,12 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
bcrypt (~> 3.1.7) bcrypt (~> 3.1.7)
bootstrap-sass (= 2.3.2.0) bootstrap-sass (= 2.3.2.0)
bootstrap-will_paginate (= 0.0.9)
byebug byebug
capybara (= 2.1.0) capybara (= 2.1.0)
coffee-rails (~> 4.1.0) coffee-rails (~> 4.1.0)
factory_girl_rails (= 4.2.0) factory_girl_rails (= 4.2.0)
faker (= 1.9.1)
jbuilder (~> 2.0) jbuilder (~> 2.0)
jquery-rails jquery-rails
pg (= 0.15.1) pg (= 0.15.1)
...@@ -223,6 +230,7 @@ DEPENDENCIES ...@@ -223,6 +230,7 @@ DEPENDENCIES
turbolinks turbolinks
uglifier (>= 1.3.0) uglifier (>= 1.3.0)
web-console (~> 2.0) web-console (~> 2.0)
will_paginate (= 3.0.4)
BUNDLED WITH BUNDLED WITH
1.17.2 1.17.2
...@@ -173,4 +173,19 @@ input { ...@@ -173,4 +173,19 @@ input {
.field_with_errors { .field_with_errors {
@extend .control-group; @extend .control-group;
@extend .error; @extend .error;
}
/* Users index */
.users {
list-style: none;
margin: 0;
li {
overflow: auto;
padding: 10px 0;
border-top: 1px solid $grayLighter;
&:last-child {
border-bottom: 1px solid $grayLighter;
}
}
} }
\ No newline at end of file
...@@ -8,7 +8,7 @@ class SessionsController < ApplicationController ...@@ -8,7 +8,7 @@ class SessionsController < ApplicationController
if user && user.authenticate(params[:session][:password]) if user && user.authenticate(params[:session][:password])
# Sign the user in and redirect to the user's show page. # Sign the user in and redirect to the user's show page.
sign_in (user) sign_in (user)
redirect_to user redirect_back_or user
else else
flash.now[:error] = 'Invalid email/password combination' # Not quite right! flash.now[:error] = 'Invalid email/password combination' # Not quite right!
render 'new' render 'new'
......
class UsersController < ApplicationController class UsersController < ApplicationController
before_action :signed_in_user, only: [:index, :edit, :update]
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])
end end
...@@ -18,9 +26,47 @@ class UsersController < ApplicationController ...@@ -18,9 +26,47 @@ class UsersController < ApplicationController
end end
end end
def edit
@user = User.find(params[:id])
end
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
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted."
redirect_to users_url
end
private private
def user_params def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation) params.require(:user).permit(:name, :email, :password, :password_confirmation)
end end
# Before filters
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
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
...@@ -19,9 +19,22 @@ module SessionsHelper ...@@ -19,9 +19,22 @@ module SessionsHelper
@current_user ||= User.find_by(remember_token: remember_token) @current_user ||= User.find_by(remember_token: remember_token)
end end
def current_user?(user)
user == current_user
end
def sign_out def sign_out
current_user.update_attribute(:remember_token, User.digest(User.new_remember_token)) current_user.update_attribute(:remember_token, User.digest(User.new_remember_token))
cookies.delete(:remember_token) cookies.delete(:remember_token)
self.current_user = nil self.current_user = nil
end end
def redirect_back_or(default)
redirect_to(session[:return_to] || default)
session.delete(:return_to)
end
def store_location
session[:return_to] = request.url if request.get?
end
end end
module UsersHelper module UsersHelper
# Returns the Gravatar (http://gravatar.com/) for the given user. # Returns the Gravatar (http://gravatar.com/) for the given user.
def gravatar_for(user) def gravatar_for(user, options = { size: 50 })
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}" size = options[:size]
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,14 +7,14 @@ ...@@ -7,14 +7,14 @@
<li><%= link_to "Home", home_path %></li> <li><%= link_to "Home", home_path %></li>
<li><%= link_to "Help", help_path %></li> <li><%= link_to "Help", help_path %></li>
<% if signed_in? %> <% if signed_in? %>
<li><%= link_to "Users", '#' %></li> <li><%= link_to "Users", users_path %></li>
<li id="fat-menu" class="dropdown"> <li id="fat-menu" 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 "Sign out", signout_path, method: :delete %> <%= link_to "Sign out", signout_path, method: :delete %>
......
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation, "Confirm Password" %>
<%= f.password_field :password_confirmation %>
\ No newline at end of file
<li>
<%= gravatar_for user, size: 52 %>
<%= 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="span6 offset3">
<%= form_for(@user) do |f| %>
<%= render 'fields', f: f %>
<%= f.submit "Save changes", class: "btn btn-large btn-primary" %>
<% end %>
<%= gravatar_for @user %>
<a href="http://gravatar.com/emails">change</a>
</div>
</div>
\ No newline at end of file
<% provide(:title, 'All users') %>
<h1>All users</h1>
<%= will_paginate %>
<ul class="users">
<%= render @users %>
</ul>
<%= will_paginate %>
\ No newline at end of file
...@@ -4,20 +4,7 @@ ...@@ -4,20 +4,7 @@
<div class="row"> <div class="row">
<div class="span6 offset3"> <div class="span6 offset3">
<%= form_for(@user) do |f| %> <%= form_for(@user) do |f| %>
<%= render 'shared/error_messages' %> <%= render 'fields', f: f %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation %>
<%= f.submit "Create my account", class: "btn btn-large btn-primary" %> <%= f.submit "Create my account", class: "btn btn-large btn-primary" %>
<% end %> <% end %>
</div> </div>
......
class AddAdminToUsers < ActiveRecord::Migration
def change
add_column :users, :admin, :boolean, default: false
end
end
...@@ -11,15 +11,16 @@ ...@@ -11,15 +11,16 @@
# #
# 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: 20200702072601) do ActiveRecord::Schema.define(version: 20200706063440) do
create_table "users", force: :cascade do |t| create_table "users", force: :cascade do |t|
t.string "name" t.string "name"
t.string "email" t.string "email"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.string "password_digest" t.string "password_digest"
t.string "remember_token" t.string "remember_token"
t.boolean "admin", default: false
end end
add_index "users", ["email"], name: "index_users_on_email", unique: true add_index "users", ["email"], name: "index_users_on_email", unique: true
......
namespace :db do
desc "Fill database with sample data"
task populate: :environment do
User.create!(name: "Example User",
email: "example@railstutorial.org",
password: "foobar",
password_confirmation: "foobar",
admin: true)
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
end
end
\ No newline at end of file
FactoryGirl.define do FactoryGirl.define do
factory :user do factory :user do
name "Michael Hartl" sequence(:name) { |n| "Person #{n}" }
email "michael@example.com" sequence(:email) { |n| "person_#{n}@example.com"}
password "foobar" password "foobar"
password_confirmation "foobar" password_confirmation "foobar"
factory :admin do
admin true
end
end end
end end
\ No newline at end of file
...@@ -93,10 +93,24 @@ describe User do ...@@ -93,10 +93,24 @@ describe User do
before { @user.password = @user.password_confirmation = "a" * 5 } before { @user.password = @user.password_confirmation = "a" * 5 }
it { should be_invalid } it { should be_invalid }
end end
it { should respond_to(:password_confirmation) } it { should respond_to(:password_confirmation) }
it { should respond_to(:remember_token) } it { should respond_to(:remember_token) }
it { should respond_to(:authenticate) } it { should respond_to(:authenticate) }
it { should respond_to(:admin) }
it { should be_valid }
it { should_not be_admin }
describe "with admin attribute set to 'true'" do
before do
@user.save!
@user.toggle!(:admin)
end
it { should be_admin }
end
describe "remember token" do describe "remember token" do
before { @user.save } before { @user.save }
its(:remember_token) { should_not be_blank } its(:remember_token) { should_not be_blank }
......
...@@ -26,21 +26,89 @@ describe "AuthenticationPages" do ...@@ -26,21 +26,89 @@ describe "AuthenticationPages" do
describe "with valid information" do describe "with valid information" do
let(:user) { FactoryGirl.create(:user) } let(:user) { FactoryGirl.create(:user) }
before do before { sign_in user }
fill_in "Email", with: user.email.upcase
fill_in "Password", with: user.password
click_button "Sign in"
end
it { should have_title(user.name) } it { should have_title(user.name) }
it { should have_link('Users', href: users_path) }
it { should have_link('Profile', href: user_path(user)) } it { should have_link('Profile', href: user_path(user)) }
it { should have_link('Settings', href: edit_user_path(user)) }
it { should have_link('Sign out', href: signout_path) } it { should have_link('Sign out', href: signout_path) }
it { should_not have_link('Sign in', href: signin_path) } it { should_not have_link('Sign in', href: signin_path) }
describe "followed by signout" do describe "followed by signout" do
before { click_link "Sign out" } before { click_link "Sign out" }
it { should have_link('Sign in') } it { should have_link('Sign in') }
end end
end end
end end
describe "authorization" do
describe "for non-signed-in users" do
let(:user) { FactoryGirl.create(:user) }
describe "when attempting to visit a protected page" do
before do
visit edit_user_path(user)
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
end
describe "after signing in" do
it "should render the desired protected page" do
expect(page).to have_title('Edit user')
end
end
end
describe "in the Users controller" do
describe "visiting the edit page" do
before { visit edit_user_path(user) }
it { should have_title('Sign in') }
end
describe "submitting to the update action" do
before { patch user_path(user) }
specify { expect(response).to redirect_to(signin_path) }
end
describe "visiting the user index" do
before { visit users_path }
it { should have_title('Sign in') }
end
end
end
describe "as wrong user" do
let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: "wrong@example.com") }
before { sign_in user, no_capybara: true }
describe "submitting a GET request to the Users#edit action" do
before { get edit_user_path(wrong_user) }
specify { expect(response.body).not_to match(full_title('Edit user')) }
specify { expect(response).to redirect_to(root_url) }
end
describe "submitting a PATCH request to the Users#update action" do
before { patch user_path(wrong_user) }
specify { expect(response).to redirect_to(root_url) }
end
end
describe "as non-admin user" do
let(:user) { FactoryGirl.create(:user) }
let(:non_admin) { FactoryGirl.create(:user) }
before { sign_in non_admin, no_capybara: true }
describe "submitting a DELETE request to the Users#destroy action" do
before { delete user_path(user) }
specify { expect(response).to redirect_to(root_url) }
end
end
end
end end
...@@ -54,4 +54,85 @@ describe "User pages" do ...@@ -54,4 +54,85 @@ describe "User pages" do
end end
end end
end end
describe "edit" do
let(:user) { FactoryGirl.create(:user) }
before do
sign_in user
visit edit_user_path(user)
end
describe "page" do
it { should have_content("Update your profile") }
it { should have_title("Edit user") }
it { should have_link('change', href: 'http://gravatar.com/emails') }
end
describe "with invalid information" do
before { click_button "Save changes" }
it { should have_content('error') }
end
describe "with valid information" do
let(:new_name) { "New Name" }
let(:new_email) { "new@example.com" }
before do
fill_in "Name", with: new_name
fill_in "Email", with: new_email
fill_in "Password", with: user.password
fill_in "Confirm Password", with: user.password
click_button "Save changes"
end
it { should have_title(new_name) }
it { should have_selector('div.alert.alert-success') }
it { should have_link('Sign out', href: signout_path) }
specify { expect(user.reload.name).to eq new_name }
specify { expect(user.reload.email).to eq new_email }
end
end
describe "index" do
let(:user) { FactoryGirl.create(:user) }
before do
sign_in user
visit users_path
end
it { should have_title('All users') }
it { should have_content('All users') }
describe "pagination" do
before(:all) { 30.times { FactoryGirl.create(:user) } }
after(:all) { User.delete_all }
it { should have_selector('div.pagination') }
it "should list each user" do
User.paginate(page: 1).each do |user|
expect(page).to have_selector('li', text: user.name)
end
end
end
describe "delete links" do
it { should_not have_link('delete') }
describe "as an admin user" do
let(:admin) { FactoryGirl.create(:admin) }
before do
sign_in admin
visit users_path
end
it { should have_link('delete', href: user_path(User.first)) }
it "should be able to delete another user" do
expect do
click_link('delete', match: :first)
end.to change(User, :count).by(-1)
end
it { should_not have_link('delete', href: user_path(admin)) }
end
end
end
end end
...@@ -5,4 +5,18 @@ def full_title(page_title) ...@@ -5,4 +5,18 @@ def full_title(page_title)
else else
"#{base_title} | #{page_title}" "#{base_title} | #{page_title}"
end end
end
def sign_in(user, options={})
if options[:no_capybara]
# Sign in when not using Capybara.
remember_token = User.new_remember_token
cookies[:remember_token] = remember_token
user.update_attribute(:remember_token, User.digest(remember_token))
else
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
end
end end
\ No newline at end of file
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