この前作った(そして、やっと先週末から稼働できるようになった)メール配信プログラムで似たような事をしました。メールについて、利用者が「新規作成」後に「申請」し、管理者が「承認」するとメールが配信されるという仕組みで、状態も「下書き」->「保留中」->「配信待ち」(ジョブに入っていて、配信されると「配信済み」になる)という感じで遷移するというものです。他にも状態(「配信失敗」とか)があって、それぞれ権限や移れる状態が異なるため、結構複雑な処理が必要でした。
そこで、アクセス管理にpunditを採用し、アクションを制限することで許可なき遷移が行われないようにしました。punditはポリシーベースのアクセス管理で、ユーザーとレコードの状態を組み合わせて、アクションに対する制限が掛けられます。状態を変更する処理はそれぞれ別アクションにしました。「申請」ボタンを押すと「申請」のアクションが呼び出され、状態が「下書き」から「保留中」に変わるという感じです。そして「承認」自体ができるかどうかをpunditでユーザー(メールの作成者かどうか)とメールの状態(「下書き」かどうか)から可否を判定するというものです。画面側でも、この可否を使って、ボタンを表示する・しないをわけています。
Hogeモデルに対して「未確認(unverified)」から「確認待ち(pending_verification)」にする「申請(apply)」を設定するのであれば、次のような感じになるでしょう。(実際に下のコードを試したわけではありませんので、詳しい使い方は各マニュアルを参考にしてください。)
config/routes.rb
Ruby
1Rails.application.routes.draw do
2 resources :hoge do
3 member do
4 put'apply'
5 end
6 end
7end
app/policies/hoge_policy.rb
Ruby
1class HogePolicy < ApplicationPolicy
2 def apply?
3 record.status.name == 'unverified'
4 end
5end
app/controllers/hoges_controller.rb
class HogesController < ApplicationController
before_action :set_hoge, only: [:show, :edit, :update, :destroy, :apply, ...]
def apply
@hoge.update(status: Status.find_by_name('pending_verification'))
# その他必要な処理をしていく…
end
def set_hoge
@hoge = Hoge.find(params[:id])
authorize @hoge
end
end
app/views/hoges/show.html.erb
ERB
1<% if policy(@hoge).apply? %>
2 <%= form_for(@hoge, url: apply_hoge_path(@hoge), method: :put) do |f| %>
3 ...
4 <%= f.submit '申請' %>
5 <% end %>
6<% end %>
なお、アクションを介さなかった場合、つまり、コンソールで直接モデルを操作した場合等は制限されません。punditによる制限の範囲外だからです。イレギュラーな事態が発生したとき、DB直接ではなくコンソールからモデルを操作できるようにするため、そのような制限は設けませんでした。
これがベストプラクティスなのかと言われると私にはわかりません。最初はアクションをわけない方法を考えていたのですが、punditでポリシー管理するならわけた方が書きやすいと言うことに気付いて、このようになりました。まだ、稼働して一週間ぐらいで、4月の本運用に向けて試験運用中のものですのですし、もしかしたら、大きな問題があるかも知れません。作ったものは組織内で使うWebアプリであるため、売り物として耐えられる設計なのかは不明です。