読者です 読者をやめる 読者になる 読者になる

Rails Webook

自社のECを開発している会社で働いています。Rails情報やサービスを成長させる方法を書いていきます

Railsのログイン認証gemのDeviseのカスタマイズ方法

Rails gem

https://camo.githubusercontent.com/b1c21cc10f2f94857dea5135fe55f2e4d451e028/68747470733a2f2f7261772e6769746875622e636f6d2f706c617461666f726d617465632f6465766973652f6d61737465722f6465766973652e706e67

Deviseで認証機能を使う、Viewの編集、日本語化、sign_insign_outのURLの変更などdeviseのカスタマイズについて説明します。

実際に試す場合は、前記事を実施済みだと差分を埋め合わせる必要がなくなり簡単です。

  1. Deviseでのアクセス制限をする
  2. DeviseのViewをカスタマイズする
  3. Deviseで出力される文字列の日本語化
  4. デバイスの画面をルートに設定
  5. URL(Route)のカスタマイズ
  6. Userモデルにusernameカラムを追加
  7. Deviseのテストヘルパー

動作確認

・Rails 4.1.4
・Devise 3.2.4


目次

  1. Deviseでのアクセス制限をする
  2. DeviseのViewをカスタマイズする
  3. Deviseで出力される文字列の日本語化
  4. デバイスの画面をルートに設定
  5. URL(Route)のカスタマイズ
  6. Userモデルにusernameカラムを追加
  7. Deviseのテストヘルパー

Deviseでのアクセス制限をする

Homeのshow画面へのアクセス制限を追加してみます。
ログインしてない状態でも、http://localhost:3000/home/showにアクセスできます。
f:id:nipe880324:20140802220516p:plain:w320

Controllerに次のようにアクセス制限を追加します。

class HomeController < ApplicationController
  # ユーザがログインしていないと"show"にアクセスできない
  before_action :authenticate_user!, only: :show

  def index
  end

  def show
  end
end

では、ログインしていない状態のまま、http://localhost:3000/home/showにアクセスしてみましょう。
アクセスすると、「You need to sign in or sigh up before continuing.」と表示され「Sign in画面」にリダイレクトされます。
f:id:nipe880324:20140802220816p:plain:w320

このように、アクセス制限を追加できます。
ちなみに、異なるユーザの場合表示させないわけではないので、そこは実装が必要です。


DeviseのViewをカスタマイズする

Sign in画面などdeviseのViewの見た目を変えたいというときがあると思います。
ここでは、Viewの変え方について説明します。

次のコマンドを実施することにより、app/views/devise配下にdeviseのViewが作成されます。

$ rails g devise:views
     invoke  Devise::Generators::SharedViewsGenerator
      create    app/views/devise/shared
      create    app/views/devise/shared/_links.erb
      invoke  form_for
      create    app/views/devise/confirmations
      create    app/views/devise/confirmations/new.html.erb
      create    app/views/devise/passwords
      create    app/views/devise/passwords/edit.html.erb
      create    app/views/devise/passwords/new.html.erb
      create    app/views/devise/registrations
      create    app/views/devise/registrations/edit.html.erb
      create    app/views/devise/registrations/new.html.erb
      create    app/views/devise/sessions
      create    app/views/devise/sessions/new.html.erb
      create    app/views/devise/unlocks
      create    app/views/devise/unlocks/new.html.erb
      invoke  erb
      create    app/views/devise/mailer
      create    app/views/devise/mailer/confirmation_instructions.html.erb
      create    app/views/devise/mailer/reset_password_instructions.html.erb
      create    app/views/devise/mailer/unlock_instructions.html.erb

この作成されたファイルを編集することにより、deviseのViewの表示をカスタマイズすることができます。
ちなみに各フォルダの対応は次のようになっています。

  • app/views/devise/sessions/new.html.erb ... ログイン画面
  • app/views/devise/registrations/new.html.erb ... ユーザ登録画面
  • app/views/devise/registrations/edit.html.erb ... ユーザ情報変更画面
  • app/views/devise/passwords/new.html.erb ... パスワードを変更するためのメールを送信する画面
  • app/views/devise/passwords/edit.html.erb ... パスワードを変更する画面
  • app/views/devise/confirmations/new.html.erb ... メールによるConfirmをする画面
  • app/views/devise/unlocks/new.html.erb ... アカウントのアンロック画面


試しに、「Sign in画面」のh2タグのSign inを日本語に変更してみましょう。

# app/views/devise/sessions/new.html.erb
<h2>ログイン</h2>

<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
...
...

では、変更後の画面を見てましょう。ページ上部が「ログイン」と表示されているでしょうか。
f:id:nipe880324:20140802221957p:plain:w320

今回は小さな変更でしたが、HTMLやCSSをいじることにより、自分の好きなログイン画面やユーザ登録画面を表示することが可能です。


Deviseで出力される文字列の日本語化

日本語化する箇所は2つあります。

  • 上記で行った「DeviseのViewをカスタマイズする」で作成した、Viewファイルのハードコーディングされた英語の文字列を日本語に変更する
  • コントローラーやモデルが出力するメッセージを日本語化するために、ja.ymldevise.ja.ymlを作成する

です。

2番目の日本語化について実施していきます。

ja.ymldevise.ja.ymlを作成

幸いなことに、他の方が作ったja.ymldevise.ja.ymlがありますのでその内容をコピーします。

Railsのdefaul_localeを日本語に設定

アプリケーションのデフォルトのロケールを"日本語"に設定します。

# config/application.rb
...
  class Application < Rails::Application
  	...
    # config.i18n.default_locale = :de
    config.i18n.default_locale = :ja
  end
end

サーバを再起動して、画面を確認するとログイン時などの文字列が日本語になっていると思います。
f:id:nipe880324:20140802223537p:plain:w320


デバイスの画面をルートに設定

デバイスの画面をホーム画面(root :to)に設定するには次のように記載します。

# config/route.rb
  ...
  # ログイン画面をホームにする
  devise_scope :user do
    root :to => "devise/sessions#new"
  end
  ...

ちなみに、rake routesコマンドで「デバイスの画面」と「コントローラーとアクション」の対応付けを確認できます。

$ rake routes
                  Prefix Verb   URI Pattern                    Controller#Action
                    root GET    /                              devise/sessions#new
        new_user_session GET    /users/sign_in(.:format)       devise/sessions#new
            user_session POST   /users/sign_in(.:format)       devise/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)      devise/sessions#destroy
           user_password POST   /users/password(.:format)      devise/passwords#create
       new_user_password GET    /users/password/new(.:format)  devise/passwords#new
      edit_user_password GET    /users/password/edit(.:format) devise/passwords#edit
                         PATCH  /users/password(.:format)      devise/passwords#update
                         PUT    /users/password(.:format)      devise/passwords#update
cancel_user_registration GET    /users/cancel(.:format)        devise/registrations#cancel
       user_registration POST   /users(.:format)               devise/registrations#create
   new_user_registration GET    /users/sign_up(.:format)       devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)          devise/registrations#edit
                         PATCH  /users(.:format)               devise/registrations#update
                         PUT    /users(.:format)               devise/registrations#update
                         DELETE /users(.:format)               devise/registrations#destroy

URL(Route)のカスタマイズ

deviseのデフォルトのURLが気に入らない場合、変更することが可能です。
deviseのデフォルトのURLは次のようになっています。

$ rake routes
                  Prefix Verb   URI Pattern                    Controller#Action
        new_user_session GET    /users/sign_in(.:format)       devise/sessions#new
            user_session POST   /users/sign_in(.:format)       devise/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)      devise/sessions#destroy
           user_password POST   /users/password(.:format)      devise/passwords#create
       new_user_password GET    /users/password/new(.:format)  devise/passwords#new
      edit_user_password GET    /users/password/edit(.:format) devise/passwords#edit
                         PATCH  /users/password(.:format)      devise/passwords#update
                         PUT    /users/password(.:format)      devise/passwords#update
cancel_user_registration GET    /users/cancel(.:format)        devise/registrations#cancel
       user_registration POST   /users(.:format)               devise/registrations#create
   new_user_registration GET    /users/sign_up(.:format)       devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)          devise/registrations#edit
                         PATCH  /users(.:format)               devise/registrations#update
                         PUT    /users(.:format)               devise/registrations#update
                         DELETE /users(.:format)               devise/registrations#destroy

今回は、

  • サインイン画面は、/users/sign_inから/users/login
  • サインアウト処理は、/users/sign_outから/users/logout

に変えてみます。

変更方法は簡単で、routes.rbファイルを次のように変更させるだけです。

# config/routes.rb
  devise_for :users, path_names: { sign_in: "login", sign_out: "logout"}

では、$rake routesコマンドでURLを確認してみましょう。
「URL Pattern」がloginlogoutになっています。

$ rake routes
                  Prefix Verb   URI Pattern                    Controller#Action
        new_user_session GET    /users/login(.:format)         devise/sessions#new
            user_session POST   /users/login(.:format)         devise/sessions#create
    destroy_user_session DELETE /users/logout(.:format)        devise/sessions#destroy
    ...
    ...


Userモデルにusernameカラムを追加

deviseはemailpasswordを使ってログインをします。
これを、usernamepasswordに変える方法を説明します。

まず、テーブルにusernameを追加します。

$ rails g migration add_username_to_users username:string

usernameは、ログイン時のキーとなるので、indexの追加と一意制約を追加しておきます。

# db/migrate/20140802133822_add_username_to_users.rb
class AddUsernameToUsers < ActiveRecord::Migration
  def change
    add_column :users, :username, :string
    add_index :users, :username, unique: true
  end
end

では、マイグレートを実施します。

$ rake db:migrate

次に、deviseの設定ファイルで認証キーをemailからusernameに変えます。

# config/initializers/devise.rb
  ...
  # ==> Configuration for any authentication mechanism
  # Configure which keys are used when authenticating a user. The default is
  # just :email. You can configure it to use [:username, :subdomain], so for
  # authenticating a user, both parameters are required. Remember that those
  # parameters are used only when authenticating and not when retrieving from
  # session. If you need permissions, you should implement that in a before filter.
  # You can also supply a hash where the value is a boolean determining whether
  # or not authentication should be aborted when the value is not present.
  # config.authentication_keys = [ :email ]
  config.authentication_keys = [ :username ]
  ...

UserモデルにValidateを追加します。

class User < ActiveRecord::Base
  ...

  validates :username, presence: true, uniqueness: true
end


ログイン画面のemailusernameの入力フィールドに変更します。

# app/views/devise/sessions/new.html.erb
<h2>ログイン</h2>

<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
  <div><%= f.label :username %><br />
  <%= f.text_field :username, autofocus: true %></div>

  <div><%= f.label :password %><br />
    <%= f.password_field :password, autocomplete: "off" %></div>

  <% if devise_mapping.rememberable? -%>
    <div><%= f.check_box :remember_me %> <%= f.label :remember_me %></div>
  <% end -%>

  <div><%= f.submit "Sign in" %></div>
<% end %>

<%= render "devise/shared/links" %>

サインアップ画面も対応しておきましょう。"username" のフィールドを追加します。

# app/views/devise/registrations/new.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= devise_error_messages! %>

  <div><%= f.label :username %><br />
  <%= f.text_field :username, autofocus: true %></div>

  <div><%= f.label :email %><br />
  <%= f.email_field :email %></div>

  <div><%= f.label :password %><br />
    <%= f.password_field :password, autocomplete: "off" %></div>

  <div><%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "off" %></div>

  <div><%= f.submit "Sign up" %></div>
<% end %>

<%= render "devise/shared/links" %>

同じく、プロフィール変更画面も、"username"を追加します。

# app/views/devise/registrations/edit.html.erb
<h2>Edit <%= resource_name.to_s.humanize %></h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
  <%= devise_error_messages! %>

  <div><%= f.label :username %><br />
  <%= f.text_field :username, autofocus: true %></div>

  <div><%= f.label :email %><br />
  <%= f.email_field :email %></div>

  <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
    <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
  <% end %>

  <div><%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
    <%= f.password_field :password, autocomplete: "off" %></div>

  <div><%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "off" %></div>

  <div><%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
    <%= f.password_field :current_password, autocomplete: "off" %></div>

  <div><%= f.submit "Update" %></div>
<% end %>

<h3>Cancel my account</h3>

<p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %></p>

<%= link_to "Back", :back %>

既にユーザが登録されているため、usernameに"testuser"という値をコンソールから入力します。
もちろん、実際に変更するときは、サインインやプロフィール変更などの画面の修正は忘れずに修正してください。

$ rails c
Loading development environment (Rails 4.1.4)
irb(main):001:0> User.first
=> #<User id: 2, email: "test@example.com", encrypted_password: "$2a$10$Oabg0hmZDismBJ2pxtfIxO9MK04/KQ7QtjnITlWXL83...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 6, current_sign_in_at: "2014-07-31 08:51:22", last_sign_in_at: "2014-07-31 08:50:50", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", created_at: "2014-07-31 08:18:45", updated_at: "2014-07-31 08:51:22", username: nil>
> User.first.update_attribute(:username, "testuser")
=> true

では、ログイン画面を開いてログインしましょう。
"Username"が表示されていると思います。
f:id:nipe880324:20140802230848p:plain:w320
そして、ログインができます。
f:id:nipe880324:20140802230853p:plain:w320


もしdevelopment.logUnpermitted parametersが出た場合は、"Strong Parameter"のエラーのため、下記のコードを追加して下さい。
":username"の箇所は、エラー内容で表示されている変数名に変えて下さい。

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  # deviceのコントローラーのときに、下記のメソッドを呼ぶ
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

    def configure_permitted_parameters
      # sign_inのときに、usernameも許可する
      devise_parameter_sanitizer.for(:sign_in) << :username
      # sign_upのときに、usernameも許可する
      devise_parameter_sanitizer.for(:sign_up) << :username
      #  account_updateのときに、usernameも許可する
      devise_parameter_sanitizer.for(:account_update) << :username
    end
end

Deviseのテストヘルパー

deviseは、テストで有用なテストヘルパーを提供しています。
それの簡単な使い方について説明します。

まず、テストにdeviseのテストヘルパーを追加します。

デフォルトのTestフレームワークを使っている場合

# test/test_helper.rb 
class ActiveSupport::TestCase
  ...
  include Devise::TestHelpers
  ...
end
RSpecを使っている場合
# spec/spec_helper.rb 
RSpec.configure do |config|
  ...
  config.include Devise::TestHelpers, type: :controller
  ...
end

これで、以下のメソッドがControllerの中で利用ができます

sign_in :user, @user   # sign_in(scope, resource)
sign_in @user          # sign_in(resource)

sign_out :user         # sign_out(scope)
sign_out @user         # sign_out(resource)

テストにおいて、以下の2点ほど注意点があります。

  1. CapybaraWebratなどで実行されるインテグレーションテストでは動作しません。機能テストのみでしか使えません。フォーム入力やセッションに値を設定することで代替して下さい。
  2. Deviseの内部のテストやDeviseのコントローラを継承したコントローラの機能テストをする場合、リクエストをする前に、mapping情報を記載しなければなりません。これは、Deviseはrouteからmapping情報を取得しますが、機能テストではrouteを通らないのでmapping情報を知るすべがないからです。例として、user scopeのテストをする場合は、次のように記載します。
@request.env["devise.mapping"] = Devise.mappings[:user]
get :new

まとめ

Deviseの一通りのカスタマイズ方法について学べたと思います。これで、Deviseを使って何かするときはほぼ問題ないと思います。
次は、「DeviseとOmniAuth-Twitter連携(Twitterでログインする)」です。