# Rails 權限系統
- [source code](https://github.com/cellvinchung/demo_permission)
## ERD
![](https://i.imgur.com/pIdt8wd.png)
## gems
- [Devise](https://github.com/heartcombo/devise)
- [Pundit](https://github.com/varvet/pundit)
## models
### User
```ruby=
class User < ApplicationRecord
belongs_to :user_group
has_many :enabled_permissions, through: :user_group
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
end
```
> 只管認證authentication,權限交由user_group處理
---
### UserGroup
```ruby
class UserGroup < ApplicationRecord
has_many :users
has_many :group_permissions, dependent: :destroy
accepts_nested_attributes_for :group_permissions
has_many :permission_resources, through: :group_permissions
has_many :enabled_permissions, -> { enable }, class_name: 'GroupPermission'
has_many :enabled_resources, through: :enabled_permissions
end
```
> user_group設定權限,user指派user_group套用
---
### GroupPermission
```ruby
class GroupPermission < ApplicationRecord
PRIORITY_OPTIONS = {
disable: 0,
readable: 1,
writable: 2
}
belongs_to :user_group
belongs_to :permission_resource
scope :enable, -> { where('group_permissions.priority > 0') }
enum priority: PRIORITY_OPTIONS
before_create :setup_priority
private
def setup_priority
self.priority = permission_resource.default_priority if priority.nil?
end
end
```
> 群組與各資源間的關聯與權限設定
---
### PermissionResource
```ruby
class PermissionResource < ApplicationRecord
has_many :group_permissions, dependent: :destroy
enum default_priority: GroupPermission::PRIORITY_OPTIONS
after_create :setup_group_sermissions
private
def setup_group_sermissions
UserGroup.all.each do |user_group|
user_group.permission_resources += [self]
end
end
end
```
> 被管理的目標資源
## Policies
> by action設定權限
### ApplicationPolicy
``` ruby
# frozen_string_literal: true
# frozen_string_literal: true
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
@user = user
@record = record
@record_model = @record.model_name.to_s
end
def index?
readable?
end
def show?
readable?
end
def create?
writable?
end
def new?
create?
end
def update?
writable?
end
def edit?
update?
end
def destroy?
writable?
end
def super_admin?
@super_admin ||= @user.user_group.super_admin?
end
def readable?
super_admin? || permissions.where(permission_resources: { name: @record_model }).any?
end
def writable?
super_admin? || permissions.writable.where(permission_resources: { name: @record_model }).any?
end
def permissions
@permissions ||= @user.enabled_permissions.includes(:permission_resource)
end
class Scope
def initialize(user, scope)
@user = user
@scope = scope
end
def resolve
scope.all
end
private
attr_reader :user, :scope
end
end
```
## Controller
### ApplicationController
```ruby
class ApplicationController < ActionController::Base
include Pundit
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized
flash[:alert] = "You are not authorized to perform this action."
redirect_back(fallback_location: root_path)
end
end
```
### AuthedController
```ruby
class AuthedController < ApplicationController
before_action :authenticate_user!
before_action :setup_authorize
private
def setup_authorize
# 預設model名為controller名的單數
authorize self.class.name.demodulize.to_s.gsub('Controller', '').singularize.constantize
end
end
```
> 需要納入權限的controller,這裡用authed
### UserGroupsController
```ruby
class UserGroupsController < AuthedController
helper_method :user_group
def index
@user_groups = UserGroup.all
end
def show
@users = user_group.users
end
def new
@user_group = UserGroup.new
# 讓view產生畫面
PermissionResource.all.each do |permission_resource|
@user_group.group_permissions.new(permission_resource: permission_resource, priority: permission_resource.default_priority)
end
end
def create
@user_group = UserGroup.new(permit_attribute)
@user_group.save
redirect_to url_for(action: :index)
end
def edit
end
def update
user_group.update(permit_attribute)
redirect_to url_for(action: :index)
end
def destroy
user_group.destroy
redirect_to url_for(action: :index)
end
private
def user_group
@user_group ||= UserGroup.includes(:permission_resources).find(params[:id])
end
def permit_attribute
params.require(:user_group).permit(
:name,
:super_admin,
group_permissions_attributes: %i[id priority permission_resource_id]
)
end
end
```
## View
### header
```htmlembedded=
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/">Home</a>
</li>
<% if user_signed_in? %>
<li class="nav-item">
<%= link_to User.model_name.human, users_path, class: 'nav-link' if policy(User).index?%>
</li>
<li class="nav-item">
<%= link_to UserGroup.model_name.human, user_groups_path, class: 'nav-link' if policy(UserGroup).index?%>
</li>
<li class="nav-item">
<%= link_to PermissionResource.model_name.human, permission_resources_path, class: 'nav-link' if policy(PermissionResource).index?%>
</li>
<% else %>
<li class="nav-item">
<%= link_to '登入', new_user_session_path, class: 'nav-link' %>
</li>
<% end %>
</ul>
</div>
</div>
</nav>
```