NetBeansで作成中のRailsプロジェクトにRestful Authenticationを追加する
- はじめに
- 今回の前提
- 認証モデル
- Restful Authenticationのインストール
- UserモデルとSessionモデルの生成
- rolesとpermissionsの設定
- コントローラーの編集
- ビューの編集
- routes.rbの編集。
- 既存のプロジェクトの修正
- 公開するときのメモ
- 戻る
はじめに_
Restful Authentication with all the bells and whistles (new 9/05/08)に従い、NetBeansで開発中のRailsプロジェクトにRestful Authenticationを付け加えるときのメモ。
今回の前提_
実施環境は以下のとおり。
- Windows XP SP2
- NetBeans 6.7.1
- Ruby 1.8.6 p287
- Rails 2.3.5
公開環境は以下のとおり
- Apache 2.2.14 (起動コマンド:/etc/init.d/apache)
- Postfix 2.6.5 (/usr/sbin/sendmail)
- passenger 2.2.7
- Ruby 1.8.7
- gem 1.3.5
- Rails 2.3.5
- SQLite3 3.6.21
認証モデル_
ユーザーの役割は二種類。管理者と一般ユーザー。このアプリケーションには、管理者だけが閲覧できるページが存在する。その他のページに関してはログイン後でないとアクセスできないとする。
つまり、Restful Authentication with all the bells and whistles (new 9/05/08)のとおりのモデル。
Restful Authenticationのインストール_
- NetBeansでプロジェクトが読み込まれているとする。プロジェクト名の上にカーソルを合わせて右クリック→「Railsプラグイン」を選ぶ。
- 開いたウィンドウで「リポジトリ」タブをクリック、その後「URLを追加」をクリックし、「http://svn.techno-weenie.net/projects/plugins」を加える。
- 「新しいプラグイン」タブをクリックし、検索ウィンドウで「restful_authentication」を検索する。
- restful_authenticationが見つかったら、選択し、インストールする(Subversionは使わなくても良い)
UserモデルとSessionモデルの生成_
restful_authenticationプラグインで使うファイルを生成する。
- NetBeansでプロジェクトが読み込まれているとする。プロジェクト名の上にカーソルを合わせて右クリック→「生成」を選ぶ。
- ジェネレーターは「authenticated」を選ぶ
- 引数は「user sessions --include-activation」を選ぶ
上記コマンドでセッション用のコントローラーとビューができているはず。その後、application_controller.rbに"include AuthenticatedSystem"が付け加えられているかをチェックする。チェックすると付け加えられていないので付け加える。
次にconfig/initalizersにmail.rbを作成する。localhostのsendmailを使う場合は、以下の内容にする。
- 「構成」をクリック
- 「initalizers」にカーソルを合わせて「新規作成」→「Rubyファイル」
- ファイル名は「mail.rb」にする。
mail.rbの中身(Sendmailバージョン)
# Email settings
ActionMailer::Base.delivery_method = :sendmail
ActionMailer::Base.sendmail_settings = {
:location => "/usr/sbin/sendmail -t"
}
次に、config/environment.rbの"Rails::Initializer.run do |config|"の後ろに以下の行を追加する。そうすると、このアプリケーションは、レジストレーションやアクティベーションなどの後にユーザーにメールを送るようになる(user_observer.rbとuser_mailer.rbはrestfulauthenticationによって、app/models以下に作られている)。
config.active_record.observers = :user_observer
次に、config/environment.rbに定数を定義するか、config/environments/development.rb(開発用設定ファイル)かconfig/environments/production.rb(公開用設定ファイル)にアプリケーション用の定数を定義する。
どのファイルに記載するかを以下の視点から決定する。
- 開発環境でも実行環境でも同じ定数を使う→config/environment.rb
- 開発環境のみでその定数を使う→config/environments/production.rb
- 実行環境のみでその定数を使う→config/environments/production.rb
具体的には以下を追加する。
## For my application RootURL = 'http://localhost:3000/' MyMailAddress = 'mail@yourapplication.com'
そして、user_mailer.rbを以下のように設定する。
user_mailer.rb:
class UserMailer < ActionMailer::Base
def signup_notification(user)
setup_email(user)
@subject += 'Please activate your new account'
@body[:url] = "#{RootURL}activate/#{user.activation_code}"
end
def activation(user)
setup_email(user)
@subject += 'Your account has been activated!'
@body[:url] = "#{RootURL}"
end
def forgot_password(user)
setup_email(user)
@subject += 'You have requested to change your password'
@body[:url] = "#{RootURL}reset_password/#{user.password_reset_code}"
end
def reset_password(user)
setup_email(user)
@subject += 'Your password has been reset.'
end
protected
def setup_email(user)
@recipients = "#{user.email}"
@from = "#{MyMailAddress}"
@subject = "YourApplication - "
@sent_on = Time.now
@body[:user] = user
end
end
config以下のファイルを書き換えた場合は、WebRickを再起動すること。
そして、user_observer.rbにパスワードのリセットとパスワード忘れ機能を組み込む。
user_observer.rb:
class UserObserver < ActiveRecord::Observer
def after_create(user)
UserMailer.deliver_signup_notification(user)
end
def after_save(user)
UserMailer.deliver_activation(user) if user.pending?
UserMailer.deliver_forgot_password(user) if user.recently_forgot_password?
UserMailer.deliver_reset_password(user) if user.recently_reset_password?
end
end
rolesとpermissionsの設定_
モデルroleとpermissionを設定する。
- roleの生成
- プロジェクト名の上にカーソルを合わせて右クリック→「生成」を選ぶ。
- ジェネレーターは scaffold
- モデル名は Role
- 属性ペアは rolename:string
- permissonの生成
- プロジェクト名の上にカーソルを合わせて右クリック→「生成」を選ぶ。
- ジェネレーターは model
- 引数は Permission
db/migrate以下にあるXXX_create_permissions.rbを編集する。
- 「データベースマイグレーション」をクリック
- 「migrate」をクリック。
- XXX_create_permissions.rbを以下のように編集。
class CreatePermissions < ActiveRecord::Migration
def self.up
create_table :permissions do |t|
t.integer :role_id, :user_id, :null => false
t.timestamps
end
#Make sure the role migration file was generated first
Role.create(:rolename => 'administrator')
#Then, add default admin user
#Be sure change the password later or in this migration file
user = User.new
user.login = "admin"
user.email = "info@yourapplication.com"
user.password = "admin"
user.password_confirmation = "admin"
user.save(false)
user.send(:activate!)
role = Role.find_by_rolename('administrator')
user = User.find_by_login('admin')
permission = Permission.new
permission.role = role
permission.user = user
permission.save(false)
end
def self.down
drop_table :permissions
Role.find_by_rolename('administrator').destroy
User.find_by_login('admin').destroy
end
end
db/migrate以下にあるXXX_create_users.rbを編集する。
- 「データベースマイグレーション」をクリック
- 「migrate」をクリック。
- XXX_create_users.rbを以下のように編集。
class CreateUsers < ActiveRecord::Migration
def self.up
create_table "users", :force => true do |t|
t.column :login, :string
t.column :email, :string
t.column :crypted_password, :string, :limit => 40
t.column :salt, :string, :limit => 40
t.column :created_at, :datetime
t.column :updated_at, :datetime
t.column :remember_token, :string
t.column :remember_token_expires_at, :datetime
t.column :activation_code, :string, :limit => 40
t.column :activated_at, :datetime
t.column :password_reset_code, :string, :limit => 40
t.column :enabled, :boolean, :default => true
end
end
def self.down
drop_table "users"
end
end
次に、app/models以下のファイルをいくつか変更しなければならない。 はじめに、rolesとusersの間の関係を定義する。rolesとusersは多対多の関係なので、has_many :through宣言で関係を示す。
- 「モデル」をクリック
- role.rbを以下のように編集。
class Role < ActiveRecord::Base has_many :permissions has_many :users, :through => :permissions end
- 「モデル」をクリック
- permission.rbを以下のように編集。
class Permission < ActiveRecord::Base belongs_to :user belongs_to :role end
続いて、以下の変更をuser.rbに行なう。
- メールアドレスの長さチェックに関する変更
- userとrolesの間の関係
- あるユーザーがある役割をもっているかどうかを調べるチェック
- パスワード忘れに関する記述
- ユーザーアクティベートに関するいくつかの変更。
- 「モデル」をクリック
- user.rbを以下のように編集。
require 'digest/sha1'
class User < ActiveRecord::Base
# Virtual attribute for the unencrypted password
attr_accessor :password
validates_presence_of :login, :email
validates_presence_of :password, :if => :password_required?
validates_presence_of :password_confirmation, :if => :password_required?
validates_length_of :password, :within => 4..40, :if => :password_required?
validates_confirmation_of :password, :if => :password_required?
validates_length_of :login, :within => 3..40
validates_length_of :email, :within => 6..100
validates_uniqueness_of :login, :email, :case_sensitive => false
validates_format_of :email, :with => /(^([^@\s]+)@((?:[-_a-z0-9]+\.)+[a-z]{2,})$)|(^$)/i
has_many :permissions
has_many :roles, :through => :permissions
before_save :encrypt_password
before_create :make_activation_code
# prevents a user from submitting a crafted form that bypasses activation
# anything else you want your user to change should be added here.
attr_accessible :login, :email, :password, :password_confirmation
class ActivationCodeNotFound < StandardError; end
class AlreadyActivated < StandardError
attr_reader :user, :message;
def initialize(user, message=nil)
@message, @user = message, user
end
end
# Finds the user with the corresponding activation code, activates their account and returns the user.
#
# Raises:
# +User::ActivationCodeNotFound+ if there is no user with the corresponding activation code
# +User::AlreadyActivated+ if the user with the corresponding activation code has already activated their account
def self.find_and_activate!(activation_code)
raise ArgumentError if activation_code.nil?
user = find_by_activation_code(activation_code)
raise ActivationCodeNotFound if !user
raise AlreadyActivated.new(user) if user.active?
user.send(:activate!)
user
end
def active?
# the presence of an activation date means they have activated
!activated_at.nil?
end
# Returns true if the user has just been activated.
def pending?
@activated
end
# Authenticates a user by their login name and unencrypted password. Returns the user or nil.
# Updated 2/20/08
def self.authenticate(login, password)
u = find :first, :conditions => ['login = ?', login] # need to get the salt
u && u.authenticated?(password) ? u : nil
end
# Encrypts some data with the salt.
def self.encrypt(password, salt)
Digest::SHA1.hexdigest("--#{salt}--#{password}--")
end
# Encrypts the password with the user salt
def encrypt(password)
self.class.encrypt(password, salt)
end
def authenticated?(password)
crypted_password == encrypt(password)
end
def remember_token?
remember_token_expires_at && Time.now.utc < remember_token_expires_at
end
# These create and unset the fields required for remembering users between browser closes
def remember_me
remember_me_for 2.weeks
end
def remember_me_for(time)
remember_me_until time.from_now.utc
end
def remember_me_until(time)
self.remember_token_expires_at = time
self.remember_token = encrypt("#{email}--#{remember_token_expires_at}")
save(false)
end
def forget_me
self.remember_token_expires_at = nil
self.remember_token = nil
save(false)
end
def forgot_password
@forgotten_password = true
self.make_password_reset_code
end
def reset_password
# First update the password_reset_code before setting the
# reset_password flag to avoid duplicate email notifications.
update_attribute(:password_reset_code, nil)
@reset_password = true
end
#used in user_observer
def recently_forgot_password?
@forgotten_password
end
def recently_reset_password?
@reset_password
end
def self.find_for_forget(email)
find :first, :conditions => ['email = ? and activated_at IS NOT NULL', email]
end
def has_role?(rolename)
self.roles.find_by_rolename(rolename) ? true : false
end
protected
# before filter
def encrypt_password
return if password.blank?
self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record?
self.crypted_password = encrypt(password)
end
def password_required?
crypted_password.blank? || !password.blank?
end
def make_activation_code
self.activation_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
end
def make_password_reset_code
self.password_reset_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
end
private
def activate!
@activated = true
self.update_attribute(:activated_at, Time.now.utc)
end
end
lib以下にあるauthenticated_system.rbの変更をする。
- 「ライブラリー」をクリック
- authenticated_system.rbにnot_logged_in_required, check_role, check_administrator_roleと permission_denied を以下のように付け加える。
module AuthenticatedSystem
protected
# Returns true or false if the user is logged in.
# Preloads @current_user with the user model if they're logged in.
def logged_in?
current_user != :false
end
# Accesses the current user from the session. Set it to :false if login fails
# so that future calls do not hit the database.
def current_user
@current_user ||= (login_from_session || login_from_basic_auth || login_from_cookie || :false)
end
# Store the given user id in the session.
def current_user=(new_user)
session[:user_id] = (new_user.nil? || new_user.is_a?(Symbol)) ? nil : new_user.id
@current_user = new_user || :false
end
# Check if the user is authorized
#
# Override this method in your controllers if you want to restrict access
# to only a few actions or if you want to check if the user
# has the correct rights.
#
# Example:
#
# # only allow nonbobs
# def authorized?
# current_user.login != "bob"
# end
def authorized?
logged_in?
end
# Filter method to enforce a login requirement.
#
# To require logins for all actions, use this in your controllers:
#
# before_filter :login_required
#
# To require logins for specific actions, use this in your controllers:
#
# before_filter :login_required, :only => [ :edit, :update ]
#
# To skip this in a subclassed controller:
#
# skip_before_filter :login_required
#
def login_required
authorized? || access_denied
end
def not_logged_in_required
!logged_in? || permission_denied
end
def check_role(role)
unless logged_in? && @current_user.has_role?(role)
if logged_in?
permission_denied
else
store_referer
access_denied
end
end
end
def check_administrator_role
check_role('administrator')
end
# Redirect as appropriate when an access request fails.
#
# The default action is to redirect to the login screen.
#
# Override this method in your controllers if you want to have special
# behavior in case the user is not authorized
# to access the requested action. For example, a popup window might
# simply close itself.
def access_denied
respond_to do |format|
format.html do
store_location
flash[:error] = "You must be logged in to access this feature."
redirect_to :controller => '/session', :action => 'new'
end
format.xml do
request_http_basic_authentication 'Web Password'
end
end
end
def permission_denied
respond_to do |format|
format.html do
#Put your domain name here ex. http://www.example.com
domain_name = "http://localhost:3000"
http_referer = session[:refer_to]
if http_referer.nil?
store_referer
http_referer = ( session[:refer_to] || domain_name )
end
flash[:error] = "You don't have permission to complete that action."
#The [0..20] represents the 21 characters in http://localhost:3000
#You have to set that to the number of characters in your domain name
if http_referer[0..20] != domain_name
session[:refer_to] = nil
redirect_to root_path
else
redirect_to_referer_or_default(root_path)
end
end
format.xml do
headers["Status"] = "Unauthorized"
headers["WWW-Authenticate"] = %(Basic realm="Web Password")
render :text => "You don't have permission to complete this action.", :status => '401 Unauthorized'
end
end
end
# Store the URI of the current request in the session.
#
# We can return to this location by calling #redirect_back_or_default.
def store_location
session[:return_to] = request.request_uri
end
def store_referer
session[:refer_to] = request.env["HTTP_REFERER"]
end
# Redirect to the URI stored by the most recent store_location call or
# to the passed default.
def redirect_back_or_default(default)
redirect_to(session[:return_to] || default)
session[:return_to] = nil
end
def redirect_to_referer_or_default(default)
redirect_to(session[:refer_to] || default)
session[:refer_to] = nil
end
# Inclusion hook to make #current_user and #logged_in?
# available as ActionView helper methods.
def self.included(base)
base.send :helper_method, :current_user, :logged_in?
end
# Called from #current_user. First attempt to login by the user id stored in the session.
def login_from_session
self.current_user = User.find(session[:user_id]) if session[:user_id]
end
# Called from #current_user. Now, attempt to login by basic authentication information.
def login_from_basic_auth
authenticate_with_http_basic do |username, password|
self.current_user = User.authenticate(username, password)
end
end
# Called from #current_user. Finaly, attempt to login by an expiring token in the cookie.
def login_from_cookie
user = cookies[:auth_token] && User.find_by_remember_token(cookies[:auth_token])
if user && user.remember_token?
user.remember_me
cookies[:auth_token] = { :value => user.remember_token, :expires => user.remember_token_expires_at }
self.current_user = user
end
end
end
チュートリアルの作者曰く。permission_deniedメソッドは改良の余地があるとのこと。このメソッドの働きは、あるリソースへのアクセス権限がない場合、一つ前に見ていたページに戻るというもの(ただし、自ドメインでない場合は、自ドメインのトップに戻る)。
コントローラーの編集_
続いてcontrollerを編集する。チュートリアルでは、restful_authenticationによって生成された多くのactionをそれら自身のコントローラーに移している。目的は、restfulの振る舞いにできる限りそうようにするため。また、将来の拡張しやすいようにしておくため。
まず、users_controller.rbを編集する。
- 「コントローラ」をクリック
- users_controller.rbを以下のように編集。
class UsersController < ApplicationController
layout 'application'
before_filter :not_logged_in_required, :only => [:new, :create]
before_filter :login_required, :only => [:show, :edit, :update]
before_filter :check_administrator_role, :only => [:index, :destroy, :enable]
def index
@users = User.find(:all)
end
#This show action only allows users to view their own profile
def show
@user = current_user
end
# render new.rhtml
def new
@user = User.new
end
def create
cookies.delete :auth_token
@user = User.new(params[:user])
@user.save!
#Uncomment to have the user logged in after creating an account - Not Recommended
#self.current_user = @user
flash[:notice] = "Thanks for signing up! Please check your email to activate your account before logging in."
redirect_to login_path
rescue ActiveRecord::RecordInvalid
flash[:error] = "There was a problem creating your account."
render :action => 'new'
end
def edit
@user = current_user
end
def update
@user = User.find(current_user)
if @user.update_attributes(params[:user])
flash[:notice] = "User updated"
redirect_to :action => 'show', :id => current_user
else
render :action => 'edit'
end
end
def destroy
@user = User.find(params[:id])
if @user.update_attribute(:enabled, false)
flash[:notice] = "User disabled"
else
flash[:error] = "There was a problem disabling this user."
end
redirect_to :action => 'index'
end
def enable
@user = User.find(params[:id])
if @user.update_attribute(:enabled, true)
flash[:notice] = "User enabled"
else
flash[:error] = "There was a problem enabling this user."
end
redirect_to :action => 'index'
end
end
次にsessions_controller.rbを編集する。
- 「コントローラ」をクリック
- sessions_controller.rbを以下のように編集。
# This controller handles the login/logout function of the site.
class SessionsController < ApplicationController
layout 'application'
before_filter :login_required, :only => :destroy
before_filter :not_logged_in_required, :only => [:new, :create]
# render new.rhtml
def new
end
def create
password_authentication(params[:login], params[:password])
end
def destroy
self.current_user.forget_me if logged_in?
cookies.delete :auth_token
reset_session
flash[:notice] = "You have been logged out."
redirect_to login_path
end
protected
# Updated 2/20/08
def password_authentication(login, password)
user = User.authenticate(login, password)
if user == nil
failed_login("Your username or password is incorrect.")
elsif user.activated_at.blank?
failed_login("Your account is not active, please check your email for the activation code.")
elsif user.enabled == false
failed_login("Your account has been disabled.")
else
self.current_user = user
successful_login
end
end
private
def failed_login(message)
flash.now[:error] = message
render :action => 'new'
end
def successful_login
if params[:remember_me] == "1"
self.current_user.remember_me
cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at }
end
flash[:notice] = "Logged in successfully"
return_to = session[:return_to]
if return_to.nil?
redirect_to user_path(self.current_user)
else
redirect_to return_to
end
end
end
次にさらに二つのcontrollerを生成する。
- roleの生成
- プロジェクト名の上にカーソルを合わせて右クリック→「生成」を選ぶ。
- ジェネレーターは controller
- モデル名は Passwords
- ビューは、edit new
- Accountsの生成
- プロジェクト名の上にカーソルを合わせて右クリック→「生成」を選ぶ。
- ジェネレーターは controller
- モデル名は Accounts
- ビューは、edit
passwords_controller.rbを編集する。
class PasswordsController < ApplicationController
layout 'application'
before_filter :not_logged_in_required, :only => [:new, :create]
# Enter email address to recover password
def new
end
# Forgot password action
def create
return unless request.post?
if @user = User.find_for_forget(params[:email])
@user.forgot_password
@user.save
flash[:notice] = "A password reset link has been sent to your email address."
redirect_to login_path
else
flash[:notice] = "Could not find a user with that email address."
render :action => 'new'
end
end
# Action triggered by clicking on the /reset_password/:id link recieved via email
# Makes sure the id code is included
# Checks that the id code matches a user in the database
# Then if everything checks out, shows the password reset fields
def edit
if params[:id].nil?
render :action => 'new'
return
end
@user = User.find_by_password_reset_code(params[:id]) if params[:id]
raise if @user.nil?
rescue
logger.error "Invalid Reset Code entered."
flash[:notice] = "Sorry - That is an invalid password reset code. Please check your code and try again. (Perhaps your email client inserted a carriage return?)"
#redirect_back_or_default('/')
redirect_to new_user_path
end
# Reset password action /reset_password/:id
# Checks once again that an id is included and makes sure that the password field isn't blank
def update
if params[:id].nil?
render :action => 'new'
return
end
if params[:password].blank?
flash[:notice] = "Password field cannot be blank."
render :action => 'edit', :id => params[:id]
return
end
@user = User.find_by_password_reset_code(params[:id]) if params[:id]
raise if @user.nil?
return if @user unless params[:password]
if (params[:password] == params[:password_confirmation])
#Uncomment and comment lines with @user to have the user logged in after reset - not recommended
#self.current_user = @user #for the next two lines to work
#current_user.password_confirmation = params[:password_confirmation]
#current_user.password = params[:password]
#@user.reset_password
#flash[:notice] = current_user.save ? "Password reset" : "Password not reset"
@user.password_confirmation = params[:password_confirmation]
@user.password = params[:password]
@user.reset_password
flash[:notice] = @user.save ? "Password reset." : "Password not reset."
else
flash[:notice] = "Password mismatch."
render :action => 'edit', :id => params[:id]
return
end
redirect_to login_path
rescue
logger.error "Invalid Reset Code entered"
flash[:notice] = "Sorry - That is an invalid password reset code. Please check your code and try again. (Perhaps your email client inserted a carriage return?)"
redirect_to new_user_path
end
end
accounts_controller.rbを編集。
class AccountsController < ApplicationController
layout 'application'
before_filter :login_required, :except => :show
before_filter :not_logged_in_required, :only => :show
# Activate action
def show
# Uncomment and change paths to have user logged in after activation - not recommended
#self.current_user = User.find_and_activate!(params[:id])
User.find_and_activate!(params[:id])
flash[:notice] = "Your account has been activated! You can now login."
redirect_to login_path
rescue User::ArgumentError
flash[:notice] = 'Activation code not found. Please try creating a new account.'
redirect_to new_user_path
rescue User::ActivationCodeNotFound
flash[:notice] = 'Activation code not found. Please try creating a new account.'
redirect_to new_user_path
rescue User::AlreadyActivated
flash[:notice] = 'Your account has already been activated. You can log in below.'
redirect_to login_path
end
def edit
end
# Change password action
def update
return unless request.post?
if User.authenticate(current_user.login, params[:old_password])
if ((params[:password] == params[:password_confirmation]) && !params[:password_confirmation].blank?)
current_user.password_confirmation = params[:password_confirmation]
current_user.password = params[:password]
if current_user.save
flash[:notice] = "Password successfully updated."
redirect_to root_path #profile_url(current_user.login)
else
flash[:error] = "An error occured, your password was not changed."
render :action => 'edit'
end
else
flash[:error] = "New password does not match the password confirmation."
@old_password = params[:old_password]
render :action => 'edit'
end
else
flash[:error] = "Your old password is incorrect."
render :action => 'edit'
end
end
end
roles_controller.rbを編集。
class RolesController < ApplicationController
layout 'application'
before_filter :check_administrator_role
def index
@user = User.find(params[:user_id])
@all_roles = Role.find(:all)
end
def update
@user = User.find(params[:user_id])
@role = Role.find(params[:id])
unless @user.has_role?(@role.rolename)
@user.roles << @role
end
redirect_to :action => 'index'
end
def destroy
@user = User.find(params[:user_id])
@role = Role.find(params[:id])
if @user.has_role?(@role.rolename)
@user.roles.delete(@role)
end
redirect_to :action => 'index'
end
end
ビューの編集_
次はビューの編集。まず、最初にこのアプリケーションのレイアウトを作成する。このレイアウトは少なくとも以下を含むこと。
- 「ビュー」をクリック
- 「layout」をクリック
- application.html.erbを編集。以下を加えると、ログイン時にユーザー名が表示され、個人情報の変更(Edit Profile)、パスワードの変更(Change Password)、ログアウト(Log Out)のリンクが表示される。ログインしており、かつ、administratorならば、その旨が表示される。未ログインならば、ログイン(Log In)、アカウント作成(Sign Up)、パスワード忘れ処理(Forgot Password)が表示される。
<ul>
<% if logged_in? %>
<li>Logged in as:</li>
<li><%= link_to h(current_user.login.capitalize), user_path(current_user) %></li>
<ul>
<li><%= link_to 'Edit Profile', edit_user_path(current_user) %></li>
<li><%= link_to 'Change Password', change_password_path %></li>
<li><%= link_to 'Log Out', logout_url %></li>
</ul>
<% if current_user.has_role?('administrator') %>
<li><%= link_to 'Administer Users', users_path %></li>
<% end %>
<% else %>
<p><%= link_to 'Log In', new_session_path %>,
<%= link_to 'Sign Up', new_user_path %>,
<%= link_to 'Forgot Password?', forgot_password_path %>
</p>
<% end %>
</ul>
app/views/passwords/edit.html.erbの編集。
<h2>パスワードの変更</h2>
<% form_tag url_for(:action => "update", :id => params[:id]) do %>
Password:<br />
<%= password_field_tag :password %><br />
Confirm Password:<br />
<%= password_field_tag :password_confirmation %><br />
<%= submit_tag "Reset Your Password" %>
<% end %>
app/views/passwords/new.html.erbの編集。
<h2>パスワードの再送付</h2>
<% form_tag url_for(:action => 'create') do %>
What is the email address used to create your account?<br />
<%= text_field_tag :email, "", :size => 50 %><br />
<%= submit_tag 'Reset Password' %>
<% end %>
app/views/accounts/edit.html.erbの編集。
<% form_tag url_for(:action => "update") do %>
<p><label for="old_password" class="block">Old Password</label><br />
<%= password_field_tag 'old_password', @old_password, :size => 45 %></p>
<p><label for="password" class="block">New Password</label><br />
<%= password_field_tag 'password', {}, :size => 45 %><br />
<small>Between 4 and 40 characters</small></p>
<p><label for="password_confirmation" class="block">Confirm new password</label><br />
<%= password_field_tag 'password_confirmation', {}, :size => 45 %></p>
<%= submit_tag 'Change password' %>
<% end %>
app/views/roles/_role.html.erbの編集。
- 「ビュー」をクリック
- 「roles」にカーソルを合わせて右クリック
- 「新規作成」→「ERBファイル」
- ファイル名を「_role.html.erb」とし、以下のように編集する。
<li>
<%= role.rolename %>
<% if @user.has_role?(role.rolename) %>
<%= link_to 'remove role', user_role_url(:id => role.id, :user_id => @user.id), :method => :delete %>
<% else %>
<%= link_to 'assign role', user_role_url(:id => role.id, :user_id => @user.id), :method => :put %>
<% end %>
</li>
app/views/roles/index.html.erb:
<h2>Roles for <%=h @user.login.capitalize %></h2> <h3>Roles assigned:</h3> <ul><%= render :partial => 'role', :collection => @user.roles %></ul> <h3>Roles available:</h3> <ul><%= render :partial => 'role', :collection => (@all_roles - @user.roles) %></ul>
他のapp/views/rolesのビューに関しては削除する。
app/views/sessions/new.html.erbの編集。
<h2>Login with User ID and Password:</h2> <% form_tag session_path do %> <p><label for="login">Login</label><br/> <%= text_field_tag 'login' %></p> <p><label for="password">Password</label><br /> <%= password_field_tag 'password' %></p> <p><label for="remember_me">Remember me:</label> <%= check_box_tag 'remember_me' %></p> <p><%= submit_tag 'Log in' %><%= link_to "Sign Up", new_user_path %></p> <% end %>
app/views/user_mailer/activation.html.erbの編集。これがメールの本文。
<%=h @user.login %>, your account has been activated. To visit the site, follow the link below: <%= @url %>
app/views/user_mailer/forgot_password.html.erbの編集。これがメールの本文。
<%=h @user.login %>, to reset your password, please visit <%= @url %>
app/views/user_mailer/reset_password.html.erbの編集。これがメールの本文。
<%=h @user.login %>, Your password has been reset
app/views/user_mailer/signup_notification.html.erbの編集。これがメールの本文。
Your account has been created. Username: <%=h @user.login %> Visit this url to activate your account: <%= @url %>
app/views/users/_user.html.erbの編集。
<tr class="<%= cycle('odd', 'even') %>">
<td><%=h user.login %></td>
<td><%=h user.email %></td>
<td><%= user.enabled ? 'yes' : 'no' %>
<% unless user == current_user %>
<% if user.enabled %>
<%= link_to('disable', user_path(user.id), :method => :delete) %>
<% else %>
<%= link_to('enable', enable_user_path(user.id), :method => :put) %>
<% end %>
<% end %>
</td>
<td><%= link_to 'edit roles', user_roles_path(user) %>]</td>
</tr>
app/views/users/edit.html.erbの編集。
<h2>Edit Your Account</h2>
<p><%= link_to 'Show Profile', user_path(@user) %> | <%= link_to 'Change Password', change_password_path %></p>
<%= error_messages_for :user %>
<% form_for :user, :url => user_url(@user), :html => { :method => :put } do |f| %>
<p>Email:<br /><%= f.text_field :email, :size => 60 %></p>
<%= submit_tag 'Save' %>
<% end %>
app/views/users/index.html.erbの編集。
<h2>All Users</h2>
<table>
<tr>
<th>Username</th>
<th>Email</th>
<th>Enabled?</th>
<th>Roles</th>
</tr>
<%= render :partial => 'user', :collection => @users %>
</table>
app/views/users/new.html.erbの編集。
<%= error_messages_for :user %> <% form_for :user, :url => users_path do |f| %> <p><label for="login">Login</label><br/> <%= f.text_field :login %></p> <p><label for="email">Email</label><br/> <%= f.text_field :email %></p> <p><label for="password">Password</label><br/> <%= f.password_field :password %></p> <p><label for="password_confirmation">Confirm Password</label><br/> <%= f.password_field :password_confirmation %></p> <p><%= submit_tag 'Sign up' %></p> <% end %>
app/views/users/show.html.erbの編集。
<h2>User: <%=h @user.login %></h2> <p>Joined on: <%= @user.created_at.to_s(:long) %></p>
routes.rbの編集。_
最後にconfig/routes.rbを編集する。「ActionController::Routing::Routes.draw do |map|」から「end」の間に以下を追加する。
map.signup '/signup', :controller => 'users', :action => 'new'
map.login '/login', :controller => 'sessions', :action => 'new'
map.logout '/logout', :controller => 'sessions', :action => 'destroy'
map.activate '/activate/:id', :controller => 'accounts', :action => 'show'
map.forgot_password '/forgot_password', :controller => 'passwords', :action => 'new'
map.reset_password '/reset_password/:id', :controller => 'passwords', :action => 'edit'
map.change_password '/change_password', :controller => 'accounts', :action => 'edit'
map.resources :users, :member => { :enable => :put } do |users|
users.resource :account
users.resources :roles
end
map.resource :session
map.resource :password
public/index.htmlを削除しないとmap.rootで設定したページがトップページにならないので注意。
サイトを立ち上げるために、データベースを作る必要があるので、マイグレーションを実行する。
- プロジェクト名にカーソルを合わせ右クリック「Rakeタスクを実行」
- 「db:migrate:reset」を選択し、実行。
- fixturesを使っている場合は
- プロジェクト名にカーソルを合わせ右クリック「Rakeタスクを実行」
- 「db:fixtures:load」を選択し、実行。
あとは、ユーザ認証が必要なコントローラーにbefore_filterを付け加えるだけ。
- 「before_filter :login_required」ならば、ログインしないとそのページは見れない
- 「before_filter :check_administrator_role」ならば、ログインかつadministratorじゃないとそのページは見れない
- 「before_filter :not_logged_in_required」ならば、ログインしていない状態でないとそのページが見れない。
各種フィルターについては、lib/authenticated_system.rbを参照。新しいフィルターを付け加える場合も、上記ファイルに付け加える。
既存のプロジェクトの修正_
次の点に注意する。
- もともとUserモデルが無かった場合は、既存のモデルとUserモデル間の関係をちゃんと定義すること
- ログインしているユーザの情報はcurrent_userというメソッド(lib/authenticated_system.rbで定義されている)で取得できる。
公開するときのメモ_
- 環境変数 RAILS_ENVを「production」に設定しておくこと。
- Cシェル系の場合
setenv RAILS_ENV production
- bash系の場合
RAILS_ENV=production export RAILS_ENV
- Cシェル系の場合
- passerngerを用いた公開の場合、ソース更新の度にApacheを再起動する必要がある
- メールに関するエラーはpostfixのログ、CGIエラーはapacheのログ、Railsのエラーは、log/production.log。
- postfixのログで「fatal: Recipient addresses must be specified on the command line or via the -t option」とでていたら、sendmailの設定ミス。mail.rbの設定を見直すこと。
# Email settings ActionMailer::Base.delivery_method = :sendmail ActionMailer::Base.sendmail_settings = { :location => "/usr/sbin/sendmail -t" - postfixのログで「lost connection after HELO from localhost[127.0.0.1]」というエラーがでたら、SMTPではなく、sendmailを使った方が良い。