[rails]ActiveAdminとCanCanCanを使って管理画面に権限機能をつける


Ruby2.1.2, Rails4.1.5で確認

ActiveAdminで作成した管理画面に、ログインユーザによってアクセスできる機能を出し分ける権限機能をつけたかったので調べてみました。

以前は「cancan」と組み合わせることで権限管理を実現できたそうですが、cancanは開発が止まってしまったらしいので、Rails4対応の「cancancan」を使ってみることにしました。

ログイン等の基本的なActiveAdminの機能はすでに動作している想定で進めます。

1. Gemfileに追加

gem 'activeadmin', github: 'activeadmin'
gem 'cancancan', '~> 1.9'  # 追加

2. 権限管理用モデルを追加

一つの管理者アカウントに複数の権限を割り当てられるようにするため、

ActiveAdminにCanCanを使った権限機能を追加する(1)」を参考にさせていただき、
・admin_role
・admin_role_assign
モデルを追加します

  • モデル生成
./bin/rails g model admin_role
./bin/rails g model admin_role_assign
  • admin_roleのmigration編集
# db/migrate/xxxxxx_create_admin_roles.rb

class CreateAdminRoles < ActiveRecord::Migration
  def migrate(direction)
    super
    # migrate時にsuper_admin権限を追加
    AdminRole.create!(name: 'super_admin') if direction == :up
  end

  def change
    create_table :admin_roles do |t|
      t.string :name

      t.timestamps
    end
  end
end
  • admin_role_assignのmigration編集
# db/migrate/xxxxxx_create_admin_role_assigns.rb

class CreateAdminRoleAssigns < ActiveRecord::Migration
  def migrate(direction)
    super
    # migrate時に初期管理ユーザにsuper_admin権限を付与
    AdminRoleAssign.create!(admin_role_id: AdminRole.first.id, admin_user_id: AdminUser.first.id) if direction == :up
  end

  def change
    create_table :admin_role_assigns do |t|
      t.integer :admin_role_id
      t.integer :admin_user_id

      t.timestamps
    end

    add_index(:admin_role_assigns, :admin_role_id, { :name => :index_admin_role_assigns_on_admin_role_id })
    add_index(:admin_role_assigns, :admin_user_id, { :name => :index_admin_role_assigns_on_admin_user_id })
  end
end
  • migrate実行
./bin/rake db:migrate

3. モデルの関連付け

  • app/models/admin_role.rb
class AdminRole < ActiveRecord::Base
  has_many :admin_role_assigns
  has_many :admin_users, through: :admin_role_assigns
end
  • app/models/admin_role_assign.rb
class AdminRoleAssign < ActiveRecord::Base
  belongs_to :admin_user
  belongs_to :admin_role
end
  • app/models/admin_user.rb
class AdminUser < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable,
         :recoverable, :rememberable, :trackable, :validatable

  has_many :admin_role_assigns
  has_many :admin_roles, through: :admin_role_assigns
 
  def display_name
    self.email
  end
end

display_nameメソッドはfilter等で表示する際の表示名を表すメソッドで、display_nameメソッドを定義しなかった場合は、以下のような表示になってしまいます。

active_admin1

activeadminでは lib/active_admin/application.rb に以下のような設定が定義されており、下記リスト内のメソッドを順にチェックして該当するメソッドの結果を表示するようになっています。

    # Active Admin makes educated guesses when displaying objects, this is
    # the list of methods it tries calling in order
    setting :display_name_methods, [ :display_name,
                                      :full_name,
                                      :name,
                                      :username,
                                      :login,
                                      :title,
                                      :email,
                                      :to_s ]

モデルが該当するメソッドを持っていない時は、明示的にメソッドを追加して正しく表示されるようにします。

4. 権限の管理画面を追加

  • 権限管理ができるように管理画面を追加
./bin/rails g active_admin:resource AdminRole
  • 管理画面からデータ追加できるようにpermit_paramsを設定
# app/admin/admin_role.rb

ActiveAdmin.register AdminRole do
  permit_params :name
end

5. admin_user編集時に権限を追加できるように

  • app/admin/admin_user.rb
ActiveAdmin.register AdminUser do
  permit_params :email, :password, :password_confirmation, admin_role_ids: []

  index do
    selectable_column
    id_column
    column :admin_roles do |f|
      f.admin_roles.size > 0 ? f.admin_roles.map { |r| r.name }.join(", ") : ''
    end
    column :email
    column :current_sign_in_at
    column :sign_in_count
    column :created_at
    actions
  end

  filter :email
  filter :admin_roles
  filter :current_sign_in_at
  filter :sign_in_count
  filter :created_at

  form do |f|
    f.inputs "Admin Details" do
      f.input :email
      f.input :password
      f.input :password_confirmation
      f.input :admin_roles
    end
    f.actions
  end

  # パスワードが空の状態で編集した場合はパスワード更新しないように(can't be blank エラーの防止)
  controller do
    def update
      if params[:admin_user][:password].blank?
        params[:admin_user].delete("password")
        params[:admin_user].delete("password_confirmation")
      end
      super
    end
  end
end

6. cancanのAbilityクラスを作成

  • generateコマンドを使ってcancancan用のクラスを作成
./bin/rails g cancan:ability
  • abilityクラスで権限情報を管理

今回、管理画面にログインできるユーザは
・Dashboardにはアクセス可能
・super_admin権限を持つ管理ユーザはすべての機能を管理可能
・admin権限を持つ管理ユーザは、AdminUserの管理とAdminRoleの管理は不可
という権限管理にしました

これをAbilityクラスに書くと以下のようになります。

class Ability
  include CanCan::Ability

  def initialize(admin_user)
    admin_user.admin_roles.each { |role| send(role.name.downcase) }
    can :manage, ActiveAdmin::Page, :name => "Dashboard"
  end

  def admin
    super_admin
    cannot :manage, AdminUser
    cannot :manage, AdminRole
  end

  def super_admin
    can :manage, :all
  end
end

権限の設定方法に関しては 「Defining Abilities」 が参考になります。

2015/2/17追記

active_adminのinitializersでCanCanAdapterを許可する設定が抜けていたので追記しておきます。

# config/initializers/active_admin.rb

# コメントアウトを解除
config.authorization_adapter = ActiveAdmin::CanCanAdapter

追記ここまで

7. 確認

migrate実行時に登録されるデフォルトの管理ユーザにはsuper_admin権限が付与された状態になっているので、管理画面ログイン後、管理ユーザの編集や権限の編集が可能です。

admin権限を作成し、admin_user追加画面で以下のようにadmin権限を付与してユーザを追加すれば、ユーザ管理権限を持たない管理ユーザを作成することができます。

active_admin2

これでActiveAdminとCanCanCanを用いて、権限管理機能つきの管理画面をつくることができました。

参考