--
CharinyaKlakhang - 22 May 2012 - 13:52
การสร้าง application E-commerce part 4
บทที่ 6 : Administrivia
บทนี้เป็นบทสุดท้ายของการสร้าง Application Ecommerce ที่ผ่านมาจะเห็นได้ว่าทุกคนสามารถเข้ามาดำเนินการ และแก้ไข เปลี่ยนแปลงข้อมูล ในโปรแกรมได้ โดยไม่ต้องมี User และ Password ซึ่งยังถือว่าเป็นระบบที่ไม่สมบูรณ์
1. Run InstantRails
2. สร้างตารางชื่อ users
3. สร้าง Admin users
3.1 สร้าง model ชื่อ User
3.2 สร้าง controller ชื่อ Login
3.3 แก้ไข Login_controller.rb , สร้างเมธอด add_user()
3.4 แก้ไข views ชื่อ add_user.rhtml
4. สร้าง password
4.1 แก้ไข model ชื่อ user.rb
5. ตรวจสอบการกรอกข้อมูล
6. สร้างหน้าจอการ Login
6.1 แก้ไข login_controller.rb , เพิ่มเมธอดชื่อ login()
6.2 แก้ไข models ชื่อ user.rb
6.3 แก้ไข views ชื่อ login.rhtml
7. สร้าง Homepage ของ admin
7.1 ปรับแต่งเมนูด้านข้าง แก้ไข layouts ขื่อ admin.rhtml
7.1 แก้ไข login_controller.rb ,เพิ่มเมธอด index()
7.2 แก้ไข views ชื่อ index.rhtml
7.3 แก้ไข models ชื่อ order.rb
8. ป้องกันการเข้าถึงเว็บ ส่วน admin
8.1 แก้ไข appplication.rb
8.2 แก้ไข admin_controller.rb
8.3 แก้ไข login_controller.rb
9. ปรับแต่งการแสดงผล
8.1 แก้ไข login_controller.rb
10. แสดงรายชื่อ admin users
10.1 แก้ไข login_controller.rb , เพิ่มเมธอด list_users()
10.2 แก้ไข views ชื่อ list_users.rhtml
11. ลบรายชื่อ admin users
11.1 แก้ไข login_controller.rb ,เพิ่มเมธอด delete_user()
12. สร้างการ Logout
12.1 แก้ไข login_controler.rb เพิ่มเมธอด logout()
เราต้องให้ทุกคนที่เข้ามาใช้งานต้องเข้าสู่ฟังก์ชัน Administrative ก่อน ดังนั้นเราต้องมีตารางเก็บข้อมูลของ User ที่จะใช้งาน
สร้างตารางชื่อ users
create table users (
id int not null auto_increment,
name varchar(100) not null,
hashed_password char(40) null,
primary key (id)
);
สร้าง Admin users
เมื่อสร้างตาราง users เราต้องสร้าง Model ด้วย
สร้าง model ชื่อ User
> cd ecommerce
> ruby script/generate model User
สร้าง controller ชื่อ Login
> ruby script/generate controller Login add_user login logout delete_user list_users
จะเห็นได้ว่า มีการกำหนด action ต่างๆ ให้กับ controller Login (add_user login logout delete_user list_users)
แก้ไข Login_controller.rb , สร้างเมธอด add_user()
ดังนั้นเราจึงไปกำหนดการทำงานให้ action ดังต่อไปนี้
Action : add_user
def add_user
if request.get?
@user = User.new
else
@user = User.new(params[:user])
if @user.save
flash[:notice] = "User #{@user.name} created"
redirect_to :action => 'index'
end
end
end
อธิบาย code ใน action : add_user
- ใน method : add_user() จะสร้าง User สมาชิกในการซื้อสินค้าใน Web site
- มีการตรวจสอบ ว่า form มีข้อมูลหรือไม่ ถ้าไ่ม่มีจะหมายถึงส่งข้อมูลแบบ get แต่ถ้ามีจะเป็นแบบ post
- ถ้าเป็นแบบ get จะไม่มีการสร้าง user แต่ post จะสร้าง user โดยนำข้อมูลใน form มาจัดเก็บลง datatbase และลิงค์ไปที่หน้า index
ต่อไปเราจะสร้าง form ให้กรอกข้อมูล สำหรับการ add_user จึงต้องสร้าง
View : add_user.rhtml
แก้ไข views ชื่อ add_user.rhtml
สร้าง template
add_user.rhtml ที่
path : app/views/login/add_user.rhtml ดังนี้
<% @page_title = "Add User" -%>
<%= error_messages_for 'user' %>
<%= form_tag %>
<table>
<tr>
<td>User name:</td>
<td><%= text_field("user", "name") %></td>
</tr>
<tr>
<td>Password:</td>
<td><%= password_field("user", "password") %></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value=" ADD USER " /></td>
</tr>
</table>
<%= end_form_tag %>
สร้าง password
ในฐานข้อมูล users จะมี field ของ hased password(ไม่ใช่ password) จะเก็บ password ในรูปแบบของ hashed string เพื่อป้องกันข้อมูลที่เป็นความลับ
สังเกตว่าใน form จะรับข้อมูลมาในรูปแบบของ plain text (form ทั่วๆไป) ดังนั้น Model : user ต้องมีหน้าที่เปลี่ยนข้อมูล password ในรูปแบบ hashed password ก่อนเขียนลง database
เพราะว่า Class : User ใน Active Record Model รู้ว่าในตาราง Users มี filed : hashed_password แต่ไม่มี field : password (plain text) ดังนั้น เราต้องเขียนโปรแกรมเปลี่ยนข้อมูลที่รับจาก form (password) เป็น string ที่ถูก hashed เป็น hashed_password
จึงต้องกำหนด การเข้าถึง field ในตาราง โดยใน Ruby จะใช้
attr_accessor
class User < ActiveRecord::Base
attr_accessor :password
เราต้องแน่ใจได้ว่า hased password ได้ถูก set ค่าจาก attribute password (plain text) ก่อนที่จะถูก model จัดเก็บลง database เราสามารถใช้
hook facility เขียนใน
ActiveRecord?
Active Record จะเขียน Callback hook สำหรับการทำงานเหล่านี้ เช่น ก่อน model จะถูกตรวจสอบ(validate) , ก่อนที่ rows จะถูก save , หลังจากสร้าง row ใหม่ เป็นต้น แต่ในที่นี้จะใช้
before callback และ
after callback สำหรับจัดการ
password
ก่อนที่ user row จะถูก save เราจะใช้
before_create() hook นำ plain text password มาเข้า
SHA1 hash function และเก็บผลลัพธ์ใน attribute : hashed_password ซึ่งนั่นก็หมายความว่าคอลัมภ์ hashed_password ใน database ได้ถูก set ค่าในรูปแบบของ hashed value ก่อนที่ model จะเขียนลงไป
หลังที่ user row ถูก save เราจะใช้
affter_create() hook เคลียร์ค่า field plain text password ให้ว่าง เพราะว่า Object : user จะเก็บค่าเหล่านี้ไว้ใน session ดังนั้นเราต้องเคลียร์ค่า
แก้ไข model ชื่อ user.rb
เราสามารถเขียน code สำหรับจัดการ password ได้ดังนี้
- ที่ path : app/models/user.rb
require "digest/sha1"
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :password
def before_create
self.hashed_password = User.hash_password(self.password)
end
def after_create
@password = nil
end
private
def self.hash_password(password)
Digest::SHA1.hexdigest(password)
end
end
ตรวจสอบการกรอกข้อมูล
เพิ่ม validation สำหรับตรวจสอบข้อมูลใน form ที่ไฟล์นี้อีก 2 บรรทัด
validates_uniqueness_of :name
validates_presence_of :name, :password
- validates_uniqueness_of : ตรวจสอบว่าข้อมูลห้ามซ้ำ
- validates_presence_of : ตรวจสอบว่าจะต้องกรอกเสมอ
ขณะนี้เราสามารถเพิ่ม user(add user) ใน database ของเราได้แล้ว
เรียกไปที่
URL : http://127.0.0.1:3000/login/add_user จะเห็นหน้าจอดังนี้
เมื่อไปเปิดดูใน databse จะได้ผลดังนี้
สร้างหน้าจอการ Login
ในที่นี้ หมายถึงการสร้าง form การ Login สำหรับ admin เพื่อจัดการกับคลังสินค้า สิ่งที่ต้องมี
- form ที่ต้องกรอก user , password
- จัดเก็บการ login ในรูปแบบ session ตลอดการทำงาน จนกระทั่งมีการ logout
- ป้องกันการเข้าถึงหน้า web page admin ทาง URL (โดยการพิมพ์เรียกมาทาง URL) ซึ่งจะอนุญาติให้ผู้ที่เข้าหน้า web page นี้ได้เฉพาะ administrater เท่านั้น
แก้ไข login_controller.rb , เพิ่มเมธอดชื่อ login()
เราต้องใช้
action:login() ใน
controller:login และเก็บการ login ใน session ของ administrator โดยมีการเก็บ id ของ Object : user โดยใช้
:user_id โดยมีการเขียน code ที่
path : app/controllers/login_controller.rb ดังนี้
def login
if request.get?
session[:user_id] = nil
@user = User.new
else
@user = User.new(params[:user])
logged_in_user = @user.try_to_login
if logged_in_user
session[:user_id] = logged_in_user.id
redirect_to(:action => "index")
else
flash[:notice] = "Invalid user/password combination"
end
end
end
อธิบาย code ดังนี้
- code นี้ก็ใช้เทคนิคเดียวกันกับ method : add_user()
- มีการใ้ช้ request และ response ใน method เดียวกัน
- ใน Get จะกำหนดให้สร้าง Object : User เพื่อเตรียมข้อมูล default ของ form
- เราจะเคลียร์ session ของ user path ถ้าไม่มีการ login ที่ถูกต้อง
- ถ้า action: login ได้รับ POST data มันจะเอาข้อมูลไปเก็บไว้ใน object : user
- ใน code นี้ Object จะมีการเรียกใช้ method try_to_login() ที่จะคืนค่าออกมาเป็น Object : User ที่ได้มาจาก rows ใน database ที่มีชื่อ name และ hashed password ตรง ซึ่งใน model : user.rb ต้องมีการเขียน code ดังนี้
แก้ไข models ชื่อ user.rb
เขียนที่
path : app/models/user.rb
def self.login(name, password)
hashed_password = hash_password(password || "")
find(:first,
:conditions => ["name = ? and hashed_password = ?",
name, hashed_password])
end
def try_to_login
User.login(self.name, self.password)
end
อธิบาย code ดังนี้
- เมื่อมีการเรียกใช้ @user.try_to_login จาก controller เพื่อต้องการให้คืนค่าของ rows ที่มี name และ hashed_password ตรงกับ database
- try_to_login() จึงเป็น method ที่เขียนใน Model : user.rb (ติดต่อกับตาราง users) เพื่อดึง row ที่ต้องการออกมาส่งกลับคืนไปให้ controller ซึ่งรอรับค่าอยู่
- ภายในมีการเรียกใช้ method : login ต่อเพื่อ นำค่า plain text password มาเข้า ฟังก์ชั่น hash_password เพื่อ hash ข้อมูล ก่อนที่จะใช้ method find() สำหรับดึง row ที่มี name และ hashed_password ตรงกับค่านี้
- สรุปการทำงานไป-กลับ ดังนี้
controller ---> try_to_login() ---> login() --->find()
controller <--- try_to_login() <--- login() <--- find() return a row
แก้ไข views ชื่อ login.rhtml
เราต้องสร้างหน้าของ login (View) ใน
login.rhtml ซึ่ง code จะคล้ายกับ
view : add_user ดังนี้
เขียนที่
path : app/views/login/login.rhtml
<% @page_title = "Login" -%>
<%= error_messages_for 'user' %>
<%= form_tag %>
<table>
<tr>
<td>User name:</td>
<td><%= text_field("user", "name") %></td>
</tr>
<tr>
<td>Password:</td>
<td><%= password_field("user", "password") %></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value=" LOGIN " /></td>
</tr>
</table>
<%= end_form_tag %>
ปรับแต่งเมนูด้านข้าง
จะเห็นได้ว่ามีการเพิ่มฟังก์ชันให้กับ administor ดังเช่น add_user(),list_users(),delete_user(),logout ขึ้นมา ดังนั้นเราต้องสร้างเมนูเพื่อลิงค์ไปยังการทำงานเหล่านี้
ในที่นี้เราจะเขียนเมนู Sidebar ใน layout ของ
admin.rhtml เพื่อจัดรูปแบบให้มีความเหมาะสมมากขึ้น
แก้ไข layouts ขื่อ admin.rhtml
โดยการแก้ไขไฟล์
admin.rhtml ที่
part : app/views/layouts/admin.rhtml ดังนี้
<html>
<head>
<title>Admin: <%= controller.action_name %></title>
<%= stylesheet_link_tag "scaffold","catalog","admin", :media => "all" %>
</head>
<body>
<div id="banner">
<%= @page_title || "Admin management" %>
</div>
<div id="columns">
<div id="side">
<% if session[:user_id] -%>
<%= link_to("Products", :controller => "admin", :action => "list") %><br />
<%= link_to("Shipping", :controller => "admin", :action => "ship") %><br /><br />
<%= link_to("Add user", :controller => "login", :action => "add_user") %><br />
<%= link_to("List users", :controller => "login", :action => "list_users") %><br /><br />
<%= link_to("Log out", :controller => "login", :action => "login") %><br />
<%= link_to("Web shopping", :controller => "store", :action => "index") %><br />
<% end -%>
<br />
</div>
<div id="main">
<% if flash[:notice] -%>
<div id="notice"><%= flash[:notice] %></div>
<% end -%>
<br />
<%= @content_for_layout %>
<br /> <br /> <br /><br /><br />
</div>
</div>
</body>
</html>
</body>
</html>
สร้าง Homepage ของ admin
แก้ไข login_controller.rb ,เพิ่มเมธอด index()
เขียน action : index() ใน
part : app/controllers/login_controller.rb ดังนี้
def index
@total_orders = Order.count
@pending_orders = Order.count_pending
end
แก้ไข views ชื่อ index.rhtml
สุดท้าย เราจะสร้างหน้า
index ของ controller : login นี้ ที่
part : app/views/login/index.rhtml ดังนี้
<% @page_title = "Administer E-commerce" -%>
<h1>E-commerce Store Status</h1>
<p>
Total orders in system: <%= @total_orders %>
</p>
<p>
Orders pending shipping: <%= @pending_orders %>
</p>
แก้ไข models ชื่อ order.rb
เขียน method class ใน Model : order เพื่อ return จำนวนตัวเลขของ
pending orders หรือ จำนวน order สินค้าที่ยังไม่ได้ส่งไปให้ลูกค้า ดังนี้
ที่
part : app/models/order.rb
def self.count_pending
count("shipped_at is null")
end
ขณะนี้ เรามีส่วนของการ login แล้ว โดยทดสอบที่
URL : http://127.0.0.1:3000/login/login
ถ้า user และ password ถูกต้อง จะทำการ redirect ไปยังหน้า index()
ป้องกันการเข้าถึงเว็บ ส่วน admin (Limiting Access)
เราป้องกันไม่ให้บุคคลที่ไม่ใช่ Administor เข้าหน้าของ admin โดยการใช้
Rails filter
Rails filter จะป้องกัน user เรียกไปที่ action method โดยไม่ต้อง login ดังนั้นควรเพิ่มกระบวนการทำงานนี้ก่อนเพื่อความปลอดภัย
ในที่นี้จะใช้
before filter ที่จะเป็นตัวป้องกันการเข้าถึงโดยการเรียกไปที่ action method ในส่วนของ
controller : admin ซึ่งตัวป้องกันนี้(interceptor) สามารถตรวจสอบจาก
session[user:id]
ถ้าเราตั้งค่าให้ application นี้รู้ว่า admin ต้องได้ทำการ login เข้ามาอย่างถูกต้อง จึีงอนุญาติให้ทำงานใน action ต่างๆต่อไปได ถ้าไม่ใช่จะ redirect กลับไปหน้าของ login ใหม่
เราควรวางตัว filter นี้ไว้ที่
controller : admin (เพราะเราต้องการป้องกันไม่ให้ส่วนนี้ ถูกเข้าถึงจากบุคคลอื่นที่ไม่ใช่ admin) ด้วยเหตุผลนี้เราจึงจะวางไว้ที่
ApplicationContoller ซึ่งเป็น
parent class ของ controller ทุกตัวใน Application (ECommerce) นี้
แก้ไข appplication.rb
ดังนั้น เราจะเขียนในไฟล์
application.rb ที่
part: app/controllers/application.rb ดังนี้
def authorize
unless session[:user_id]
flash[:notice] = "Please log in"
redirect_to(:controller => "login", :action => "login")
end
end
Method
Authorization นี้ สามารถถูกเรียกให้ทำงานก่อน action ทุกตัว ซึ่งในที่นี้เราต้องการให้มีผลต่อการจัดการกับ
controller : admin โดยการเพิ่มเพียงแค่ 1 บรรทัดเท่านั้น
แก้ไข admin_controller.rb
เพิ่ม code ใน
admin_controller.rb ที่
part : app/controller/admin_controller.rb ดังนี้
class AdminController < ApplicationController
before_filter :authorize
...
เพียงเท่านี้การทำงานของ
controller : admin จะไม่สามารถเรียก action method ได้ทาง URL แล้วจะต้อง login เป็น admin อย่างถูกต้อง จึงสามารถทำงานที่ action อื่นๆใน controller นี้ได้
และเราต้องการให้ทำงานเช่นเดียวกันที่
controller : login แต่จะอนุญาติให้ทำงานที่บาง action ได้ คือ
action : login() เพราะต้องกรอกชื่อ user และ password ในหน้านี้
แก้ไข login_controller.rb
ดังนั้นเราจะเพิ่ม code ใน
login_controller.rb ที่
part : app/controller/login_controller.rb
class LoginController < ApplicationController
before_filter :authorize, :except => :login
...
ปรับแต่งการแสดงผลของ login
เพื่อแสดงข้อความเตือน (Error) , banner , menu side เป็นต้น
แก้ไข login_controller.rb
class LoginController < ApplicationController
layout "admin"
...
เมื่อเรียกไปที่
URL: http://127.0.0.1:3000/admin หรือ action อื่นๆ ทั้งใน
controller : admin และ
controller : login (ยกเว้น action : login) application จะ redirect ไปที่
URL : http://127.0.0.1:3000/login/login เสมอ ดังภาพ
แสดงรายชื่อ admin users
ในที่นี้จะต้องมีการแสดง รายชื่อ admin ทั้งหมดที่เราได้ทำการ add user เอาไว้ โดยการเพิ่ม code ดังนี้
แก้ไข login_controller.rb , เพิ่มเมธอด list_users()
def list_users
@all_users = User.find(:all)
end
แก้ไข views ชื่อ list_users.rhtml
<% @page_title = "User List" -%>
<table cellpadding="5" cellspacing="0" border=1 width=55%>
<tr align=center>
<th>Name</th>
<th>Delete</th>
</tr>
<% for user in @all_users -%>
<tr align=center>
<td><%= user.name %></td>
<td><%= link_to "(delete)", {:action => :delete_user, :id => user.id },:confirm => 'Are you sure?' %></td>
</tr>
<% end -%>
</table>
</table>
ลบรายชื่อ admin users
เราต้องมี function ของการลบข้อมูล user admin หาก accout นั้นไม่ได้ใช้แล้ว ในที่นี้จะลบผ่าน list_users.rhtml
แก้ไข login_controller.rb ,เพิ่มเมธอด delete_user()
def delete_user
id = params[:id]
if id && user = User.find(id)
user.destroy
flash[:notice] = "User #{user.name} deleted"
end
redirect_to(:action => :list_users)
end
สร้างการ Logout
เมื่อ user admin ต้องการออกจากระบบ ดังนั้น เราต้องเขียน code ที่ไฟล์
login_controller.rb
แก้ไข login_controler.rb เพิ่มเมธอด logout()
def logout
session[:user_id] = nil
flash[:notice] = "Logged out"
redirect_to(:action => "login")
end