railsでログインシステム
railsでログインシステムを試してみたのでメモ.
コード自体は「Railsによるアジャイルwebアプリケーション開発」の第14章タスク:ログインとほぼ同じ.
本格的なログイン機能が必要な場合プラグインを使う.Deviseというプラグインが人気らしい.
環境はRuby1.9.3p286とRails3.2.8
Gemfileに
gem 'bcrypt-ruby'
を追加してinstallしておく
$rails g controller home index
しておく,index.html.erbを編集する.
index.html.erb
<h1>ログインシステムのテスト</h1> <ul> <li><a href="#">ログイン</a></li> <li><a href="#">ログアウト</a></li> </ul>
webアプリには管理者と一般ユーザがいる.Userモデルが必要である.今回はこのUserモデルが管理人のことなので注意.一般ユーザモデルは実装しない.セッションを持続するためにSessionモデルも必要である.まずUserモデルを生成する.scaffoldを使えばOK
$rails g scaffold User name:string password_digest:string $rake db:migrate
user.rbを編集する.
user.rb class User < ActiveRecord::Base attr_accessible :name, :password validates :name, presence: true, uniqueness: true has_secure_password end
ユーザにshowメソッドは必要ないので新規登録(user/new)のあとshowにリダイレクトするのはやめる.user_conrtoller.rbを編集する.
createdメソッド後のredirect_to @userをredirect_to users_urlに変更,updateメソッドも同様,users_urlとはusers/indexのことindexはUser.allをUser.order(:name)とすることでアルファベット順に並び替える.
user_controller.rb
# GET /users # GET /users.json def index @users = User.order(:name) respond_to do |format| format.html # index.html.erb format.json { render json: @users } end end # POST /users # POST /users.json def create @user = User.new(params[:user]) respond_to do |format| if @user.save format.html { redirect_to users_url, notice: 'User was successfully created.' } format.json { render json: @user, status: :created, location: @user } else format.html { render action: "new" } format.json { render json: @user.errors, status: :unprocessable_entity } end end end # PUT /users/1 # PUT /users/1.json def update @user = User.find(params[:id]) respond_to do |format| if @user.update_attributes(params[:user]) format.html { redirect_to users_url, notice: 'User was successfully updated.' } format.json { head :no_content } else format.html { render action: "edit" } format.json { render json: @user.errors, status: :unprocessable_entity } end end end
ビューを変更する.notice(お知らせ)を追加し,password_digestの表示を削除する.
users/index.html.erb
<h1>Listing users</h1> <% if notice %> <p id="notice"><%= notice %></p> <% end %> <table> <tr> <th>Name</th> <th></th> <th></th> <th></th> </tr> <% @users.each do |user| %> <tr> <td><%= user.name %></td> <td><%= link_to 'Show', user %></td> <td><%= link_to 'Edit', edit_user_path(user) %></td> <td><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <% end %> </table> <br /> <%= link_to 'New User', new_user_path %>
パスワードのダイジェストのフォームをパスワードを確認のフォームに置き換える
users/_form.html.erb
<%= form_for(@user) do |f| %> <% if @user.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2> <ul> <% @user.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <fieldset> <legend>ユーザ情報の入力</legend> <div> <%= f.label :name %>: <%= f.text_field :name, size: 40 %> </div> <div> <%= f.label :password, 'パスワード' %>: <%= f.password_field :password, size: 40 %> </div> <div> <%= f.label :password_confirmaion, '確認' %> <%= f.password_field :password_confirmaion, size: 40 %> </div> <div> <%= f.submit %> </div> </fieldset> <% end %>
testを更新しておく.
users_controller_test.rb
require 'test_helper' class UsersControllerTest < ActionController::TestCase setup do @input_attributes = { name: "sam", password: "private", password: "private" } @user = users(:one) end test "should get index" do get :index assert_response :success assert_not_nil assigns(:users) end test "should get new" do get :new assert_response :success end test "should create user" do assert_difference('User.count') do post :create, user: @input_attributes end assert_redirected_to users_path end test "should show user" do get :show, id: @user assert_response :success end test "should get edit" do get :edit, id: @user assert_response :success end test "should update user" do put :update, id: @user.to_param, user: @input_attributes assert_redirected_to users_path end test "should destroy user" do assert_difference('User.count', -1) do delete :destroy, id: @user end assert_redirected_to users_path end end
管理者のページのため,セッション管理のためのコントローラを生成する.newはログイン画面のコントローラ,createはログインのコントローラ,viewは持たない.destroyはセッション終了のコントローラ,viewは持たない.
$rails g controller Sessions new create destroy $rails g controller Admin index
createアクションはAdminがログイン済みであるかを確認する.もしログインがすんでいればsessionに記録しなおす.ログインがまだならnewアクションにリダイレクトし,ログインを促す.セッションにはsession[キー]でアクセスできる.destroyはセッションを終了する
sessions_controller.rb
class SessionsController < ApplicationController def new end def create user = User.find_by_name(params[:name]) if user and user.authenticate(params[:password]) session[:user_id] = user.id redirect_to admin_url else redirect_to login_url, alert: "error" end end def destroy session[:user_id] = nil; redirect_to users_url end end
newアクションはログインページを提供する.
sessions/new.html.erb
<% if flash[:alert] %> <p id="notice"><%= flash[:alert] %></p> <% end %> <%= form_tag do %> <fieldset> <legend>ログインしてください</legend> <div> <%= label_tag :name, '名前:' %> <%= text_field_tag :name, params[:name] %> </div> <div> <%= label_tag :password, 'パスワード' %> <%= password_field_tag :password, params[:password] %> </div> <div> <%= submit_tag 'ログイン' %> </div> </fieldset> <% end %>
routes.rbを編集してレスポンスをrailsに知らせてやる必要がある
routes.rb
Login::Application.routes.draw do get 'admin' => 'admin#index' controller :sessions do get 'login' => :new post 'login' => :create delete 'logout' => :destroy end #…
アクセス制限機能を実装する.railsのフィルタ機能を使うと全てのページで共通の処理を実装することが出来る.通常は ApplecationContorllerにbefore_filterをかける.authorizefメソッド内部でsessionがあるかを調べ,無かったら/loginにリダイレクトする.
ApplectionController.rb
class ApplicationController < ActionController::Base protect_from_forgery before_filter :authorize private def authorize unless User.find_by_id(session[:user_id]) redirect_to login_url, notice: "Please login" end end end
このままではログインしなければwebサイトの機能を使うことができない.ログイン不要な部分はskip_before_filter機能を使い,スキップする.ログインページはskip_before_filterをしておかないと無限ループになり,ブラウザがアクセスを拒否することもあるようだ.
home_controller.rb
class HomeController < ApplicationController skip_before_filter :authorize def index end end
sessions-controller.rb
class SessionsController < ApplicationController skip_before_filter :authorize def new end def create user = User.find_by_name(params[:name]) if user and user.authenticate(params[:password]) session[:user_id] = user.id redirect_to admin_url else redirect_to login_url, alert: "error" end end def destroy session[:user_id] = nil; redirect_to users_url end end