Ruby on Railsの認証 - Devise ユーザー名で認証 + 簡単なRSpec
Railsの認証を行うためのgemに、Deviseというものがあります。
デフォルトではメールアドレスとパスワードで認証を行うため、ユーザー名でログインするサンプルが
なかなか見つからなかったのですが、RailsCastsで見つけたので、実際にやってみました。
以下、今回参考にしたRailsCastsの記事です。
http://railscasts.com/episodes/209-devise-revised?view=asciicast
(日本語訳は以下の2記事が該当しますが、多少古い情報かもしれません。)
http://ja.asciicasts.com/episodes/209-introducing-devise
http://ja.asciicasts.com/episodes/210-customizing-devise
上記の記事を参考にし、以下の点に手を加えました。
・既存のRailsアプリではなく、新規のRailsアプリを作り、Deviseを適用した。
・認証済みユーザーに公開するページは、home/edit画面とした。
・外見はTwitter Bootstrap を使用した。
・RSpecによるテストを追加した。またテストし易いように一部のソースを変えた。
今回作成したアプリは、GitHub上に載せておきました。参考にして下さい。
https://github.com/UnderSourceCode/devise_name_auth_sample
では、手を加えた箇所を中心に、手順を書いていきたいと思います。
■Railsアプリの作成〜Twitter Bootstrapの適用
1.アプリの新規作成
以下のコマンドで、アプリを作成します。
- T オプションで、デフォルトのテストを作成しないようにしています。
$ rails new devise_name_auth_sample -T
2.「Could not find a JavaScript runtime.」というエラー対策
アプリを起動すると、エラーが発生します。JavaScriptのRuntimeが必要なので
以下の2行をGemfileに追加して、bundle install します。
gem 'therubyracer'
gem 'execjs'
3.rspec、spork、capybara等の準備
RSpecの実行に必要なものをインストールするため、以下をGemfileに追記して
bundle install します。
group :development, :test doこれ以降の手順については、RSpecの自分なりの使用方法を参考にしてください。
gem 'rspec-rails'
gem 'spork'
gem 'factory_girl_rails'
gem 'capybara'
end
4.twitter bootstrapの準備
以下をGemfileに追記して、bundle install します。
gem "twitter-bootstrap-rails"これ以降の手順については、twitter-bootstrap-railsの導入を参考にしてください。
gem "less-rails"
■homeコントローラの作成
新規アプリなので、認証済みのユーザーのみに公開するページ、全てのユーザーに公開するページ
を作る必要があります。
今回はhomeコントローラに、index、editという2つのViewをつくり、indexは全てのユーザーに公開
editは認証済みのユーザーにのみ公開することにしました。
1.コントローラの作成
以下のコマンドで、コントローラとViewを作成します。
$ rails g controller home index edit
2.URL Rootの設定
routes.rbを変更し、indexをルート画面とします。
root to: 'home#index' #追加
#"home/index" ###コメントアウト or 消去
3.ルート画面の確認
/public/index.htmlを削除し、rails s でアプリを起動します。
home#index画面が表示されることを確認します。
■deviseの準備
1.インストール
以下をGemfileに追記して、bundle install します。
gem 'devise'
2.deviseのインストーラを起動
以下のコマンドを実行します。
$ rails g devise:install
3.deviseのインストーラのメッセージを読み、必要と思われるものを行う
deviseのインストーラを起動すると、メッセージが表示されます。
その中から、必要と思われる手順を実行します。
今回は、config/environments/development.rbに以下を追加だけ行いました。
onfig.action_mailer.default_url_options = { :host => 'localhost:3000' }
※メッセージを表示する箇所をapplication.html.erbに追加する手順は行いませんでした。
理由は、Twitter Bootstrapでメッセージ表示領域が既に追加されているからです。
application.html.erbの以下の行が、メッセージ表示領域となります。
<%= bootstrap_flash %>
4.userモデルを作成
以下のコマンドを実行します。
$ rails g devise user
5.データベースにテーブルを作成
以下のコマンドを実行します。
$ rake db:migrate
■Viewの作成
1.Login, Sign up, Logoutリンクの作成
ユーザーがログイン等の操作を行うためのリンクを作成します。
参考にしたRailsCastsの記事では、/app/views/layouts/application.html.erbにリンクを作成していますが
今回はビューヘルパーに作成しました。
理由は、ビューヘルパーに外出しすることでテストをし易くするためです。
以下が、ビューヘルパーのソースです。/view/sharedフォルダを作成し、その中に作成します。
_user_nav.html.erb
<div id="user_nav">見ての通り、内容はRailsCastsと同じです。
<% if user_signed_in? %>
Logged in as <strong><%= current_user.email %></strong>.
<%= link_to 'Edit profile', edit_user_registration_path %> |
<%= link_to "Logout", destroy_user_session_path, method: :delete %>
<% else %>
<%= link_to "Sign up", new_user_registration_path %> |
<%= link_to "Login", new_user_session_path %>
<% end %>
</div><% flash.each do |name, msg| %>
<%= content_tag :div, msg, id: "flash_#{name}" %>
<% end %>
以下が、application.html.erbの一部抜粋です。
<%= render 'shared/user_nav' %>でビューヘルパーを参照しています。
application.html.erb
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="btn btn-navbar" data-target=".nav-collapse" data-toggle="collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="#">DeviseNameAuthSample</a><%= render 'shared/user_nav' %>
</div>
</div>
</div>
2.認証のためのビューを作成
以下のコマンドで、認証に必要なdeviseのビューを作成します。
$ rails g devise:views
3./app/views/devise/sessions/new.html.erbを変更
ログイン画面を変更します。ソースについては、RailsCastsの「/app/views/devise/sessions/new.html.erb」
を参考にしてください。
4.login, logoutのパスを/config/routes.rbに定義
以下のdevise_for〜で始まる定義を、routes.rbに追加します。
routes.rb
DeviseNameAuthSample::Application.routes.draw do
devise_for :users, path_names: {sign_in: "login", sign_out: "logout"}root to: 'home#index'
get "home/edit"
end
■ユーザー名でログインできるようにする
上記の手順では、メールアドレスでログインするようになっているので
ユーザー名でログインできるように変更します。
この手順についても、RailsCastsと同じなので、RailsCastsの「Changing Authentication Fields」を
参考にしてください。
■テストの作成
最後になりましたが、RSpecによるテストの作成です。実は、ここが一番作りたかった箇所でもあります。
1.spec/factories.rbの作成
spec/factories.rbファイルを作成し、FactoryGirlsに読み込ませるため
ダミーのユーザー情報を定義します。
spec/factories.rb
require 'factory_girl'FactoryGirl.define do
factory :user do
username 'UnderSourceCode'
email 'UnderSourceCode@ggg.com'
password 'password'
password_confirmation "password"
end
end
2.spec/support/controller_macros.rbの作成
spec/support/controller_macros.rbファイルを作成し、RSpecでログイン状態を再現するための処理を
記述します。
spec/support/controller_macros.rb
module ControllerMacros
def login_user
user = FactoryGirl.create(:user)
sign_in user
return user
end
end
3.spec/spec_helper.rbに以下を追加
RSpecでdeviseのヘルパーと、上記のControllerMacrosを使用するための定義を追加します。
spec/spec_helper.rb
config.include Devise::TestHelpers, :type => :controller
config.include Devise::TestHelpers, :type => :view
config.include ControllerMacros, :type => :controller
config.include ControllerMacros, :type => :view
4.テスト用データベースの作成
以下のコマンドで、テストで使うデータベースをコピーして作成します。
$ rake db:test:clone
5.Userモデルのテスト
Userモデルのテストです。今回はusernameが入力必須であることをテストしました。
Userモデルについては、RailsCastsの記事を参考にしてください。
spec/models/user_spec.rb
require 'spec_helper'describe User do
it '全てを入力した場合は正常に保存されること' do
@user = User.new(:email => 'test@test.com',
:password => 'password',
:password_confirmation => 'password',
:remember_me => true,
:username => 'test')
result = @user.save
result.should equal(true)
@user.should be_valid
endit '名前を入力しない場合は保存されないこと' do
@user = User.new(:email => 'error@test.com',
:password => 'errorpass',
:password_confirmation => 'errorpass',
:remember_me => true,
:username => '')
result = @user.save
result.should equal(false)
@user.should_not be_valid
end
end
6.Login, Sign up, Logoutリンクのテスト
ビューヘルパーに外出ししたリンクのテストです。
ログイン済みかどうかで表示するリンクが異なるので、その辺りをテストします。
spec/vies/shared/_user_nav.html.erb_spec.rb
require 'spec_helper'describe "shared/_user_nav.html.erb" do
describe "ログインしている場合" do
before(:each) do
user = login_user
view.stub(:current_user).and_return(user)
render ("shared/user_nav")
endit "ログアウトのリンクが表示されていること" do
rendered.should have_selector('a', 'Logout')
end
enddescribe "ログインしていない場合" do
before(:each) do
render ("shared/user_nav")
endit "サインアップのリンクが表示されていること" do
rendered.should have_selector('a', 'Sign up')
endit "ログインのリンクが表示されていること" do
rendered.should have_selector('a', 'Login')
end
end
end
以上です。
■追記
別のプロジェクトにこの手順でDeviseを導入したところ
以下のエラーは発生しました。
Factory already registered: user
対応策とては、自動で作成される/spec/factories/users.rbを
削除することでした。