【備忘録】Rails6 Devise + cancancan の使い方


はじめに

deviseを使用してUserモデル(認証)を作成後して
cancancanを追加して認可をできるようにしました。

その過程を備忘録としてログします。

deviseのgemを追記します。

・
・
・
gem 'devise'

バンドルインストールします。

$bundle install

デバイスをインストールします。

$rails generate devise:install

設定があるか確認します。

config/environments/development.rb


config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

page#indexを追記します。

config/routes.rb
Rails.application.routes.draw do
root to: "page#index"


end

以下を追記します。

app/views/layouts/application.html.erb

<body>
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
・
・
</body>

Viewをインストールします。

$ rails g devise:views

以下が作られます。

app/views/devise/unlocks/new.html.erb
app/views/devise/shared/_links.html.erb
app/views/devise/shared/_error_messages.html.erb
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/mailer/unlock_instructions.html.erb
app/views/devise/mailer/reset_password_instructions.html.erb
app/views/devise/mailer/password_change.html.erb
app/views/devise/mailer/email_changed.html.erb
app/views/devise/mailer/confirmation_instructions.html.erb
app/views/devise/confirmations/new.html.erb

モデルを作成します。
今回はUserモデルを作ります。

$ rails g devise user

以下モデルが作られます。

app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
end

以下が同時に作られます。

config/application.rb
config.i18n.default_locale = :ja

以下も作られます。

config/routes.rb
ails.application.routes.draw do
devise_for :users


end

モデル作成後に、DBに反映します。

rails db:migrate

まだpage#indexのViewがないので作ります。

$ rails g controller Pages index

余談ですが、routes.rbにスコープを作ることでPathを変更できます。

routes.rb
  devise_scope :user do
    get 'login', to: 'devise/sessions#new'
    post 'login', to: 'devise/sessions/#create'
    delete 'logout', to: 'devise/sessions#destroy' 
  end

これにより、
http://localhost:3000/login
にアクセスしたときに、ログインページが表示されます。

もちろんデフォルトの/users/sign_inでもログインページが表示されるので必要であれば削除しておきましょう。

routes.rb
 devise_for :users, skip: [:sessions]

デバイスの設定は完了です。

cancancanのインストール

次にcancancanをインストールしていきます。
これは認可と言って、ユーザによって、
アクセス権を付与するという意味になります。
システム管理者と一般ユーザの違いです。

gem 'cancancan'

バンドルインストールします。

$bundle install

Abilityクラスが作成されます。

rails g cancan:ability

Abilityクラスのデフォルトです。

class Ability
  include CanCan::Ability

  def initialize(user)
    # Define abilities for the passed in user here. For example:
    #
    #   user ||= User.new # guest user (not logged in)
    #   if user.admin?
    #     can :manage, :all
    #   else
    #     can :read, :all
    #   end
    #
    # The first argument to `can` is the action you are giving the user
    # permission to do.
    # If you pass :manage it will apply to every action. Other common actions
    # here are :read, :create, :update and :destroy.
    #
    # The second argument is the resource the user can perform the action on.
    # If you pass :all it will apply to every resource. Otherwise pass a Ruby
    # class of the resource.
    #
    # The third argument is an optional hash of conditions to further filter the
    # objects.
    # For example, here the user can only update published articles.
    #
    #   can :update, Article, :published => true
    #
    # See the wiki for details:
    # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities
  end
end

以下に変更します。

# frozen_string_literal: true

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new
    can :read,  :all

    if user.admin? 
      can :manage,  :all

    end



    # Define abilities for the passed in user here. For example:
    #
    #   user ||= User.new # guest user (not logged in)
    #   if user.admin?
    #     can :manage, :all
    #   else
    #     can :read, :all
    #   end
    #
    # The first argument to `can` is the action you are giving the user
    # permission to do.
    # If you pass :manage it will apply to every action. Other common actions
    # here are :read, :create, :update and :destroy.
    #
    # The second argument is the resource the user can perform the action on.
    # If you pass :all it will apply to every resource. Otherwise pass a Ruby
    # class of the resource.
    #
    # The third argument is an optional hash of conditions to further filter the
    # objects.
    # For example, here the user can only update published articles.
    #
    #   can :update, Article, :published => true
    #
    # See the wiki for details:
    # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities
  end
end

user ||= User.new
上記がないとゲストユーザで操作しているときに、エラーになりました。

if user.admin? ←adminって何?と聞かれました。

def initialize(user)
user ||= User.new
can :read, :all
Abilityモデルが呼ばれたときに
初期値としてログインしていれば、そのユーザを変数に入れ、ログインしていなければゲストユーザが変数に代入されます。
そして、ユーザ変数に対して、read権限を付与するという意味となる。全てのユーザがread権限があるということです。

read権限というのは、CRUDに紐づいている訳ではないので注意しましょう。
ページが読めるということであり、getができるという意味ではないし、createメソッドとかも別にプログラムすればできます。

if user.admin? 
  can :manage,  :all

こちらはUserモデルに作成したadminカラムにtrueが入っていれば実行するプログラムです。
つまりは管理者権限を持つユーザということになります。

Page#indexのビューページに以下を追加します。

app/views/pages/index.html.erb
<h1>Pages#index</h1>
<p>Find me in app/views/pages/index.html.erb</p>
  <%= link_to "削除", logout_path, method: :delete %>

<% if can?  :update, current_user %>
  <h1>update</h1>

<% end %>

<% if can? :read, current_user %>
  <h1>read</h1>
<% end %>

<% if can? :update, current_user %>
今ログインしているユーザがupdateの権限を持っていれば中身を表示します。

<% if can? :read, current_user %>
もしログインしているユーザがreadの権限を持っていれば中身を表示します。

という意味になります。

db/schema.rb
  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.boolean "admin", default: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

上記でadminってUserモデルにないけどどうするの?ってなるかと思うが、
追加することとなります。

$rails g migration AddAdminToUser admin:boolean

デフォルトで以下が作成されるので
default: "false"を追加します。

ビューページでは、adminを選択するビューは作成せず、デフォルトでユーザを作成したらadmin=falseがつくようにします。
ここでは、adminは管理側で意図的に作成することとします。

db/migrate/20200522114428_add_admin_to_users.rb

class AddAdminToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :admin, :boolean, default: "false"
  end
end

マイグレートします。

$rails g migrate

DBのスキーマの確認をします。

db/schema.rb
 create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.boolean "admin", default: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

http://localhost:3000/
で一般ユーザを作成しておきます。

adminユーザは、
以下の通り作成します。

$user =User.new(id: xx, email: "xxx@yyy", password: "xxxx", admin: true)

これで手続きは完了なので、
実際に一般ユーザとAdminユーザそれぞれで、
Page#indexにアクセスします。

一般ユーザはread権限のみなのでreadを表示可能です。

Adminユーザは,manage権限(全ての権限を持つ)なのでread,updateそれぞれ表示可能です。