หลังจากที่ทำไปได้เยอะแล้ว คราวนี้จะเป็นส่วนของการ authentication(แบบง่ายๆ) โดยผมจะทำการสร้าง controller มาตัวนึง ชื่อว่า Login ซึ่ง controller ตัวนี้จะทำการ login/logout และจะรวมถึงระบบ users ด้วย โดยหน้า admin ทุกหน้า จำเป็นจะต้อง login ก่อน ถึงจะเข้ามาที่หน้านั้นๆ ได้ มาเริ่มเลย
ก่อนอื่นต้องไปสร้าง user model ก่อน ใน terminal พิมพ์ว่า
./script/generate model user
เราก็จะได้ model มาตัวนึง แล้วก็ตามด้วยการสร้างตารางบนฐานข้อมูล ตามนี้เลย
1 2 3 4 5 6 | CREATE TABLE `users` ( `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY , `name` VARCHAR( 64 ) NOT NULL , `password` VARCHAR( 64 ) NOT NULL , `email` VARCHAR( 64 ) NOT NULL ) |
แล้วก็มาที่ฝั่ง controller กันบ้าง สร้าง login_controller.rb ก่อน
./script/generate controller login
ตอนนี้ ในตาราง user ยังไม่มีอะไรเลย คงต้องเพิ่ม user ไปก่อนสักคน ตรงนี้ เราจะทำเหมือนๆ เดิม คือกรอก username กับ password เพื่อลงทะเบียน แต่เป็นฝ่าย admin ที่ทำการลงทะเบียนให้ พอฝั่ง login controller รับตัวแปรมา ก็เอามาจับใส่ฐานข้อมูล มาเริ่มกันที่ add_user กันก่อน ไปแก้ที่ app/controllers/login_controller.rb
1 2 3 4 5 6 7 8 9 10 | def add_user if request.get? @user = User.new else @user = User.new(params[:user]) if @user.save redirect_to_index("User #{@user.name} created") end end end |
ตรงนี้ก็เริ่มเหมือนๆ เดิมแล้ว คือตรวจ method ก่อนว่าเป็น get หรือเปล่า ถ้าเป็นแสดงว่าต้องแสดงหน้าที่ให้กรอกข้อมูล แต่ถ้าไม่ใช่ ก็แสดงว่ามันเป็น post เราจะทำการยัดมันลงในฐานข้อมูล
แล้วไปสร้าง form สำหรับกรอกข้อมูลพวกนี้ โดยสร้าง add_user.rhtml ที่ app/views/login/ ตามนี้
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | < %= 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 %> |
ในนี้จะมีการใช้ text_field และ password_field ซึ่งเป็น form helpers ของทาง Rails บนหน้าเว็บ มันจะถูกสร้างออกมาเป็น <input /> ก็ลอง view source ดูละกันครับ
จากนั้นมา validate ข้อมูลกันต่อที่ app/models/user.rb
1 2 3 4 5 6 7 8 9 | class User < ActiveRecord::Base validates_uniqueness_of :name validates_presence_of :name, :password protected def validate errors.add(:email, "is wrong.") unless self.email =~ /\A[\w\._%-]+@[\w\.-]+\.[a-zA-Z]{2,4}\z/ end end |
คราวนี้ จะมีการสร้าง validate ขึ้นใช้เอง โดยจะตรวจจาก validate method ซึ่งจะตรวจจาก regular expression ซึ่งตัวนี้ ซึ่ง validate นี้ จะถูกเรียกจาก rails หลังจากที่มีการ save อะไรก็ตามลงในฐานข้อมูล ผมไป copy ของชาวบ้านมาใช้ ถ้ามันไม่ตรง มันจะขึ้นว่า email is wrong บนหน้าเว็บ ตามที่เราเคยทำ validation กันไปคราวนู้นนน แต่คราวนี้ method นี้มี modifier เป็น protected หมายความว่า ไม่ให้ถูกเรียกจากข้างนอกได้ แต่ถ้าเป็น object ที่อยู่ข้างใน class นั้นๆ ซึ่งเป็นชนิดเดียวกัน ก็สามารถเรียกได้ อย่าเพิ่งงง ไว้ถึงเรื่อง Ruby เต็มๆ ก่อน ผมจะมาอธิบายให้ละเอียดกว่านี้
ผมลองมองดูคร่าวๆ แล้ว คิดว่ามีส่วนที่ต้องย้อนกลับไปที่หน้า index เยอะเลย ถ้ามี method ที่พิมพ์ทีเดียว แล้วไปหน้า index เลยก็คงดี โดยที่ไม่ต้องคอยสั่ง redirect_to แล้วก็แถม flash[:notice] ไปทุกๆ ครั้ง งั้นเรามาสร้างดีกว่า ไปสร้างที่ app/controllers/application.rb
1 2 3 4 5 6 7 | class ApplicationController < ActionController::Base private def redirect_to_index(msg = nil) flash[:notice] = msg if msg redirect_to(:action => 'index') end end |
method นี้ จะถูกเรียกใช้ได้ใน class นี้ และใน class ที่ต่อเติม(extend) จาก class นี้เท่านั้น เพราะว่าเป็น private ซึ่ง method ไหนก็ตาม ที่อยู่ต่ำกว่า private ก็จะมี modifier เป็น private หมด ยกเว้นว่าจะไปเจอ modifier ตัวอื่นซะก่อน ลองย้อนๆ กลับไปดูได้ในทุกๆ controller มันจะสืบทอด(inherite) มาจาก ApplicationController ทั้งนั้น จากนั้น มาลอง add_user กันก่อน ซึงน่าจะได้ตามนี้

ถ้า email ไม่ผ่าน น่าจะขึ้นแบบนี้
แต่ว่าเรายังไม่มี index สำหรับ login เลย กลับไปเพิ่มใน login_controller.rb ใหม่
1 2 3 | def index redirect_to :controller => "admin", :action => "index" end |
ซึ่งมันก็จะสั่ง redirect ไปที่หน้า index ของ admin นั่นเอง
หลังจากที่ทำการ add เรียบร้อยแล้ว คราวนี้ก็มาสร้างส่วนของการ login กันต่อ กลับเข้าไปที่ login_controller.rb แล้วใส่อันนี้เพิ่มเข้าไป
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 |
เมื่อมันตรวจสอบได้ว่า method เป็น get ก็จะแสดงหน้า login แต่ถ้าเป็น post ก็จะทำการตรวจสอบการ login ที่ต้องมีการตรวจสอบ method เพราะว่าเราจะใช้ชื่อ action ตัวเดียวกัน ถ้าทำการ login สำเร็จ ก็จะ redirect ไปที่หน้า admin ถ้าไม่สำเร็จ ก็ย้อนกลับไปที่หน้าเดิมพร้อมทั้งขึ้นข้อความเตือน
แล้วย้อนไปสร้าง try_to_login ใน user.rb ก่อน เพราะเราเรียกใช้มันนี่ แต่เรายังไม่ได้สร้างเลย
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class User < ActiveRecord::Base validates_uniqueness_of :name validates_presence_of :name, :password def self.login(name, password) find(:first, :conditions => ["name = ? and password = ?", name, password]) end def try_to_login User.login(self.name, self.password) end protected def validate errors.add(:email, "is wrong.") unless self.email =~ /\A[\w\._%-]+@[\w\.-]+\.[a-zA-Z]{2,4}\z/ end end |
มาดูกันหน่อย self ใน Ruby ก็เหมือนๆ กับ this แต่การใช้ self.login จะหมายความว่าทำให้มันเป็น static ซึ่งสามารถเรียกได้โดยตรงโดยผ่าน class ได้เลยเช่น User.login ไม่ต้องสร้าง instance มาใหม่เพื่อเรียกใช้งาน ถ้ามันสามารถ login ได้สำเร็จ มันก็จะส่ง user ออกมา ถ้าไม่สำเร็จก็จะส่ง nil ออกมาแทน ถ้าสำเร็จแล้วมันจะทำการเก็บใส่ใน session(ย้อนกลับไปดูใน controller ก็ได้ครับ)
ต่อไป สร้างหน้า login.rhtml ซะหน่อย ยังไม่ได้สร้างอีกเช่นเคย
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <%= 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 %> |
แล้วลองไปที่ http://localhost:3000/login/login น่าจะได้แบบนี้

สำหรับ flash[:notice] ที่เราใส่กันไว้ จะไม่แสดง จนกว่าจะเอา layout ที่เราใส่ flash[:notice] แปะเอาไว้มาแสดง ถ้าใครใจร้อนอยากลอง ก็ลองเอา flash[:notice] มาใส่ไว้ละกัน
คราวนี้ มาสร้างตัวตรวจสอบการ login กันต่อ ใน controller ที่เราต้องการใส่ premission ให้มัน เรายังไม่ได้ใส่เลย ขั้นตอนนี้ง่ายมากๆ สำหรับ Rails มันจะมี method อยู่ตัวนึงที่ชื่อว่า before_filter มันจะถูกเรียกทุกๆ ครั้งเมื่อมีการเข้าถึง controller เราก็สร้างตัวตรวจสอบมา แล้วจัดการใส่ไว้ใน before_filter นี้ ให้มันตรวจสอบทุกๆ ครั้ง ว่ามันได้ทำการ login หรือยัง ถ้ายัง ให้กลับไปหน้า login
ใน controller ของเรา เราจะใช้ admin และ login ที่ต้องการการ authentication มาสร้างตัวตรวจสอบตรงนี้กันก่อน ไปที่ app/controllers/application.rb แก้ไข code ดังนี้
1 2 3 4 5 6 7 8 9 10 11 12 13 | class ApplicationController < ActionController::Base def authorize unless session[:user_id] flash[:notice] = "Please log in" redirect_to(:controller => "login", :action => "login") end end private def redirect_to_index(msg = nil) flash[:notice] = msg if msg redirect_to(:action => 'index') end end |
จะมีส่วนที่เพิ่มขึ้นมาเอาไว้บน private ละกัน แล้วย้อนกลับไปที่ admin_controller.rb เพิ่ม before_filter เข้าไปส่วนหัวดังนี้
1 2 3 4 5 6 7 8 | class AdminController < ApplicationController before_filter :authorize def index . . . end |
ทีนี้ ทุกๆ ครั้งที่มีการเข้ามาเรียกใช้ action ใน controller ตัวนี้ มันจะวิ่งเข้าไปตรวจว่า session[:user_id] ได้ถูกตั้งค่าหรือยัง ถ้ายัง ก็กลับไปหน้า login ซึ่งมันจะถูกตั้งค่าตั้งแต่ตอน login แล้ว ลองย้อนกลับไปดู code ได้
แต่ว่า ระบบ user ผมจะเอามันไว้ใน login นี่แหละ เพราะฉะนั้น ใน login_controller.rb ก็ต้องเพิ่มเข้าไปด้วย และผมต้องการใช้ layout อันเดียวกับ admin ด้วย
1 2 3 4 5 6 7 8 9 | class LoginController < ApplicationController layout 'admin' before_filter :authorize, :except => :login def index . . . end |
แต่คราวนี้ต่างออกไป เพราะต้องทำการยกเว้น login ไว้ ไม่งั้นก็เรียกใช้ login ตอนที่ต้องการ login ไม่ได้
ยังขาด logout เพิ่มไปหน่อยที่ login_controller.rb นี่แหละ
1 2 3 4 5 | def logout session[:user_id] = nil flash[:notice] = "Logged out" redirect_to(:action => "login") end |
ก็ทำการสั่งให้ session[:user_id] มีค่าเป็น nil
เราอาจจะไปเขียนเพิ่มว่า ถ้า controller เป็น login ยังไม่ต้องแสดง navigator ก็ได้ จะได้ดูดีอีกหน่อย
พอเป็นรูปเป็นร่างมาแล้ว คราวหน้า จะมาทำการตกแต่ง sidebar ซะหน่อย ให้มันเหมือนกับ blog จริงๆ
ปล. ส่วนที่เหลือ พวกระบบ user ลองไปปรับๆ แก้ไข หรือทำให้มันดีขึ้นละกันครับ ติดอะไรตรงไหนโพสทิ้งไว้ละกันครับ
แก้ไขล่าสุด วันที่ 12 กรกฏาคม 2550 เวลา 2.32 น.

