■ 1. 権限管理の重要性とアンチパターン
- 権限管理ミスの影響: 給与や取引先情報などの機密情報公開につながり、サービスへの信頼が低下し、事業への大きな損失を招くため、ミスは許されない。
- よくあるアンチパターン:
user.admin?
やuser.super_admin?
のように役割に依存した判定を行うこと。- 役割は事業やサービスの変化(市場、企業規模、機能)に伴い変わりゆくため、役割に依存した実装は変化に弱く、権限が暗黙的になり、技術負債の原因となる。
- 推奨されるアプローチ: 役割(
admin?
)に依存せず、権限に依存した判定(例:user.can_create_project?
)にすることで、権限が明示的になり、コードリーディングや変更時の整合性確認が容易になる。■ 2. 権限管理の要素整理とPunditの問題点
- 権限管理の要素分解: 権限管理を整理する上で、以下の4つの要素に分割できる。
- 対象: Model(例:
Project
)- 操作: CRUDやその他のアクション(例:
update
)- 役割(RBAC): Role-Based Access Control(例:
管理者アカウント
、外部アカウント
)- 条件(ABAC): Attribute-Based Access Control(例:
作成者
、担当者
)- Punditの問題: Punditを利用した場合、一つのポリシーメソッド内で役割と条件の判定が混在し、複雑な判定ロジックになると、「通常ユーザーは更新できるか?」といった問いに対し、すぐに回答できない(理解で間違えやすい)という問題が生じる。
- 例:
管理者かマネージャーかつ担当者か作成者ならできる...
のような論理演算の連なりは、一目で理解しにくい。■ 3. 要素を適切に分割したModuleの実装と実現方法
- Moduleの設計思想: 役割と条件を分離し、権限を対象・操作・役割・条件の4要素で明示的に定義する。
- 実装の概要:
- ディレクトリ/ファイル構造: 対象ごとにディレクトリを作成し、その中に役割ごとにファイル(権限の具体クラス)を作成する。
- 権限の判定: 権限の具体クラス内で、対象の基底クラスで定義した条件(例:
assignee?
やauthor?
)を英語と論理演算(||
や&&
)でシンプルに組み合わせて表現する。- メタプログラミングの活用: 対象と役割から権限のクラスを特定する際に使用し、権限の追加・変更を容易にする。
- 利用時のモード:
- recordモード: 特定のレコードに対して権限があるかを判定する(例:
Context.new.can_update?(@project)
)。- scopeモード: 権限があるレコードのみに絞り込む(例:
Context.new.scope_for_read(Project)
)。- listモード: クライアント(Next.jsなど)に渡すための権限の一覧を取得する。
■ 4. 良い設計がもたらす影響
- 間違えない実装の実現: 役割と条件を分離し、権限を明示的に記述することで、実装・利用・理解のすべてのフェーズで間違いを減らす(不具合の発生件数がゼロ、社内からの問い合わせがゼロ)。
- 開発生産性の向上: CSやPdMがGitHub上のコードを直接見て理解できるようになり、エンジニアへの問い合わせ対応時間が削減された。
- クライアント(Next.js)への影響:
- Railsから渡された権限の一覧(JSON)にNext.jsが依存することで、Next.js側でも役割に依存しない実装が自然に実現した。
- 権限によるUIの制御を(例: 編集ボタンの表示/非表示)宣言的に記述できるようになり、フロントエンドの技術負債も防いでいる。