えらく多機能なので試しながらメモる.
とりあえず使う
とりあえず使ってみる.
認証をするからにはサンプルが欲しいので用意しよう.
サンプルプロジェクトを考える
メモを管理するサービスということにしよう.
コントローラは
- StaticPages(トップページ管理)
- Notes(メモの管理)
の2つを用意する.
機能は認証とメモ管理のみ.
プロジェクトを作る
$ rails new notes
Deviseのインストール
gem 'devise'
をGemfileへ書いて
$ bundle install
とりあえずトップページとメモ機能を作る
とりあえずなのでscaffoldで.
$ rails g controller StaticPages $ rails g scaffold note subject:string body:string $ rails db:migrate
StaticPagesへはconfig/routes.rbを編集してルートも追加.
Rails.application.routes.draw do root to: 'static_pages#index' #追加 resources :notes end
雑にトップページも作っておく.
<!-- app/views/static_pages/index.html.erb --> <h1>Top</h1>
ここまででメモ機能は動作する.
Deviseのセットアップ
ここからDeviseを使っていく.
Deviseを使うにはまず以下のコマンドでセットアップする.
$ rails g devise:install
そうすると丁寧に以下のメッセージを出してくれる.
=============================================================================== Some setup you must do manually if you haven't yet: 1. Ensure you have defined default url options in your environments files. Here is an example of default_url_options appropriate for a development environment in config/environments/development.rb: config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } In production, :host should be set to the actual host of your application. 2. Ensure you have defined root_url to *something* in your config/routes.rb. For example: root to: "home#index" 3. Ensure you have flash messages in app/views/layouts/application.html.erb. For example: <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> 4. You can copy Devise views (for customization) to your app by running: rails g devise:views ===============================================================================
1はメーラの設定, Deviseは認証やパスワードリマインダーにメールを使えるのでそのため. 2はルートページURLの設定, これは認証時の遷移先で必要になるだろう. 3はエラーメッセージなどを表示するタグをviewに入れてくれということ. 4はカスタムのviewが欲しい時に使えという事.
Deviseはデフォルトでviewを用意するのでカスタムする時には4のコマンドでまずはviewのファイルを生成してやらないとならない.
(viewをカスタムしない場面もあまりなさそうなものだけど)
2のルートページの設定は上述で行ったので, 1, 3を行います.
カスタムviewは後回し.
1. メーラの設定
# config/environments/development.rb # 追加 config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
3.エラーメッセージタグのviewへの追加
app/views/layout/application.html.erb
<!-- ここから --> <div class="notice"> <%= notice %> </div> <div class="alert"> <%= alert %> </div> <!-- ここまで --> <%= yield %>
認証に使うモデルの作成
認証するためのアカウント情報を保持するためのモデルを作成します.
作成にはDeviseが用意しているコマンドを使います.
$ rails g devise User
作成されたファイルを少し覗いてみます.
# app/models/user.rb class User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable
deviseの利用するモジュールがわらわら書かれています(後述)
マイグレーションファイルも見てみます.
# frozen_string_literal: true class DeviseCreateUsers < ActiveRecord::Migration[5.1] def change create_table :users do |t| ## Database authenticatable t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: "" ## Recoverable t.string :reset_password_token t.datetime :reset_password_sent_at ## Rememberable t.datetime :remember_created_at ## Trackable t.integer :sign_in_count, default: 0, null: false t.datetime :current_sign_in_at t.datetime :last_sign_in_at t.string :current_sign_in_ip t.string :last_sign_in_ip ## Confirmable # t.string :confirmation_token # t.datetime :confirmed_at # t.datetime :confirmation_sent_at # t.string :unconfirmed_email # Only if using reconfirmable ## Lockable # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts # t.string :unlock_token # Only if unlock strategy is :email or :both # t.datetime :locked_at t.timestamps null: false end add_index :users, :email, unique: true add_index :users, :reset_password_token, unique: true # add_index :users, :confirmation_token, unique: true # add_index :users, :unlock_token, unique: true end end
すごいわらわら出来てます...
コメントアウトされているものをアンコメントするとConfirmableなどのモジュールも利用できそうな気配.
とりあえずデフォルトの認証機能さえ使えれば良いのでマイグレーション.
$ rails db:migrate
サインインとサインアウトのリンクをつける
サインインとサインアウトの画面へのリンクをトップページに作りたいので, routesを確認してみます.
imai@IMAI-PC:~/lab/projects/notes$ rails 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 new_user_password GET /users/password/new(.:format) devise/passwords#new edit_user_password GET /users/password/edit(.:format) devise/passwords#edit user_password PATCH /users/password(.:format) devise/passwords#update PUT /users/password(.:format) devise/passwords#update POST /users/password(.:format) devise/passwords#create cancel_user_registration GET /users/cancel(.:format) devise/registrations#cancel new_user_registration GET /users/sign_up(.:format) devise/registrations#new edit_user_registration GET /users/edit(.:format) devise/registrations#edit user_registration PATCH /users(.:format) devise/registrations#update PUT /users(.:format) devise/registrations#update DELETE /users(.:format) devise/registrations#destroy POST /users(.:format) devise/registrations#create [中略]
とりあえずusersの部分だけ見てもたくさん生成されています.
とはいえリンクに使えそうなものは以下でしょう.
- new_user_registration
- new_user_session
- destroy_user_session
これらを使ってリンクを用意します.
トップページからのリンクを作る
ユーザ登録(sign up)とログイン(sign in)は必要でしょう.
トップページ(app/views/static_pages/index.html.erb)にリンクを加えます.
<h1>Top</h1> <%= link_to 'sign in', new_user_session_path %> <%= link_to 'sign up', new_user_registration_path %>
ログアウトのリンクを作る
ログインしたらログアウトのリンクも欲しいのでレイアウト(app/views/layouts/application.html.erb)へ加えます.
<body> <!-- ここから --> <div class="session-control-box"> <% if signed_in? %> <%= link_to 'sign out', destroy_user_session_path, method: :delete %> <% end %> </div> <!-- ここまで -->
signed_in?はdeviseが用意してくれるヘルパーメソッドです.
その名の通りログイン中だったらtrueを返します.
ログイン後のリダイレクトを設定する
ログイン後にはメモの一覧へ移動してほしいのでヘルパーメソッドの利用を追加します.
app/controllers/application_controller.rbに
- after_sign_in_path_for
- after_sign_out_path_for
の利用を追加します.
名前の通りこのメソッドの戻り値のパスへリダイレクトされます.
class ApplicationController < ActionController::Base protect_from_forgery with: :exception def after_sign_in_path_for(resource) notes_path end def after_sign_out_path_for(resource) root_path end end
これでログイン後とログアウト後の挙動を変更できます.
ログイン前はアクセスできないようにする
リダイレクトされてもログイン前にメモ管理のエリアにアクセスできてしまっては本末転倒なので, 認証機能を追加します.
class NotesController < ApplicationController before_action :authenticate_user! #追加 before_action :set_note, only: [:show, :edit, :update, :destroy]
authenticate_[モデル名]メソッドがdeviseで用意されているのでこれを使うだけです.
未認証の場合はログイン画面へリダイレクトされます.
ログインした人間のノートだけ参照できるようにする
このままではログインさえすれば誰のメモでも見れてしまうので, メモに所有者情報を持たせてログインしているユーザのメモだけが見れるように変更します.
まずはmigrationでnoteへuserへの外部キーを追加します.
$ rails g migration AddUserToNotes user:references $ rails db:migrate
モデルへアソシエーションもつけておきます.
ユーザ側.
# app/models/user.rb class User < ApplicationRecord has_many :notes
メモ側.
# app/models/note.rb class Note < ApplicationRecord belongs_to :user end
次にscaffoldで作ったコントローラをちょっと変更.
def index #[変更] #@notes = Note.all @notes = current_user.notes end [中略] def new #[変更] #@note = Note.new @note = current_user.notes.build end [中略] def create #[変更] #@note = Note.new(note_params) @note = current_user.notes.build(note_params) [中略] private # Use callbacks to share common setup or constraints between actions. def set_note #[変更] #@note = Note.find(params[:id]) @note = current_user.notes.find_by(id: params[:id]) redirect_to notes_url if @note.nil? end
current_[モデル名]ヘルパーメソッドがdeviseによって用意されるのでログイン中のユーザを取得するのは簡単です.
ここまでやると普通にアプリケーションっぽくなります.
i18nに対応する
deviseそのままでは英語メッセージが表示されるのでI18N対応してやる.
なんとそのままの名前のgemがあった.
余談だがこの方, kaminariとか色々なgemのi18n対応をやってるみたいで地味にすごい.
Gemfileに追記して更新する.
# 追記 gem 'devise-i18n'
$ bundle install
デフォルトロケールを設定していない場合はこちらも設定.
# config/application.rb # 以下を記述 config.i18n.default_locale = :ja
これだけでメッセージが日本語化される.ありがたい.
公式にも一応色々記載があるので込み入ったことをしたい場合は読むとよいかも.
専用のwikiもある.
ViewとControllerをカスタマイズする
ちょっと試すぐらいならデフォルトでも良いけど, 流石にこのままのviewでは実用に耐えない.
モデルもちょっといじって, 登録時にユーザ名も登録するようにしてみる.
Userモデルの変更
migrationでユーザ名を追加する.
$ rails g migration AddNameToUsers name:string $ rails db:migrate
deviseのviewの用意
deviseでviewをカスタマイズするにはまず以下のコマンドでビューを生成する.
$ rails g devise:views users(モデル名)
するとdeviseで使うビューが用意される.
これをカスタマイズしていく.
deviseのcontrollerの用意
controllerもviewと同様で, 下記のコマンドで生成する.
$ rails g devise:controllers users
親切にroutes.rbを変更しろと教えてくれる.
create app/controllers/users/confirmations_controller.rb create app/controllers/users/passwords_controller.rb create app/controllers/users/registrations_controller.rb create app/controllers/users/sessions_controller.rb create app/controllers/users/unlocks_controller.rb create app/controllers/users/omniauth_callbacks_controller.rb =============================================================================== Some setup you must do manually if you haven't yet: Ensure you have overridden routes for generated controllers in your routes.rb. For example: Rails.application.routes.draw do devise_for :users, controllers: { sessions: 'users/sessions' } end ===============================================================================
生成されたコントローラがapp/controllers/users/に用意されるが, これらは全てコメントアウトされている.
なのでこれをベースに使うものをアンコメントしていかないとならない.
各コントローラと機能の対応は多分以下の感じじゃないかな.
コントローラ名 | 機能 |
---|---|
confirmations_controller.rb | 登録後のメールでの確認 |
passwords_controller.rb | パスワードリマインダー |
registrations_controller.rb | ユーザ登録 |
sessions_controller.rb | セッション(ログイン/ログアウト) |
omni_auth_callbacks_controller.rb | omni_authによるOAuth制御のコールバック |
unlocks_controller.rb | ログインに一定回数失敗したらアカウントロックするアレ |
とりあえず
- ユーザ登録
- セッション管理
だけは使いたいので以下の2つのファイルの中身をアンコメントする.
- registrations_controller
- sessions_controller
routesを変更
コンソールで出た指示通りに.
#devise_for :users devise_for :users, controllers: { registrations: 'users/registrations', sessions: 'users/sessions' }
ここまででコントローラとビューをカスタムする前と同じ挙動になる.
viewの変更
app/views/users/registrations/new.html.erbを変更して名前欄を追加する.
<!-- ここから --> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name, autofocus: true %> </div> <!-- ここまで --> <div class="field">
ついでにログイン中のユーザ名を見えるようにしておく.
<!-- app/views/layout/application.html.erbへ追加 --> <p><%= current_user.name %></p>
controllerの変更
strong parametersを追加するだけですが.
def configure_sign_up_params #devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute]) devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) end
公式にstrong parametersに関する記載があったので, 困ったら読めば良いかな.
localeファイルの編集
普通にjaのymlを用意してActiveRecordの部分に書いた.
# config/locales/ja.yml ja: activerecord: attributes: user: name: ユーザ名
これだけでうまく動作します.素敵ですね.
testをしたい
ControllerのテストもIntegrationテストもどちらでもIntegrationHelpersをincludeすると
- sign_in
- sign_out
の両メソッドが利用できるので, これで困ることはほぼないと思う.
# test/controllers/note_controller_test.rb class NotesControllerTest < ActionDispatch::IntegrationTest include Devise::Test::IntegrationHelpers setup do sign_in users(:yamada) end
ちょっと調べる必要があったのがFixturesの書き方で, 以下の様に書いた.
# test/fixtures/users.yml yamada: email: yamada@example.com encrypted_password: <%= Devise::Encryptor.digest(User, 'password') %>
モジュールの一覧
deviseには以下の10個のモジュールがあり, それぞれ用途に応じて使い分ける事ができる.
モジュール名 | 機能 | デフォルト |
---|---|---|
database_authenticatable | DBに保存するパスワードの暗号化 | 有効 |
registerable | サインアップ処理 | 有効 |
recoverable | パスワードリセット機能 | 有効 |
rememberable | クッキーにログイン情報を保持する | 有効 |
trackable | サインイン回数・時刻・IPアドレスを保存 | 有効 |
validatable | メールアドレスとパスワードでのバリデーション | 有効 |
confirmable | メール送信による登録確認 | |
lockable | 一定回数ログインに失敗した際のアカウントロック | |
timeoutable | 一定時間でセッションを削除する | |
omniauthable | OmniAuthのサポート |
ちょっとすぐに全部は試せそうにない.心折れた.
とはいえこれだけでかなり使えそうです.