🎃
Policyパターン適用しようと思ったらちょっと苦戦した話
Daily Blogging98日目
before_action整理用にPolicyパターン使ってみたけど、エラー処理で苦戦した
なんとなく考えてた改善案↓
判定ロジックはPolicyパターンに集約
問題点
元々のcontrollerには、before_actionが多くてアクションの処理に集中できない問題があった
こんな感じ
before_action :set_xxx
before_action :authentication
before_action :check_xxx_1
before_action :check_xxx_2
before_action :check_xxx_3
before_action :check_xxx_4
.
.
.
def create
end
private
def set_xxx
# インスタンス変数をセット
end
def check_xxx_1
# 条件を満たさない場合は、bad_request
end
def check_xxx_2
# 条件を満たさない場合は、forbidden
end
def check_xxx_3
# 条件を満たさない場合は、not_found
end
def check_xxx_4
# 条件を満たさない場合は、bad_request
end
エラー時のstatusが共通じゃない...
最初の改善案だと、policy.comply_with_allで一括判定しようとしてる。
でも全ての条件を満たしているのか、そうでないかしかわからないのでエラー時のstatusだし分けができない
最終的にこうした
Policyクラスにerrorを持たせて、条件をPASSできなかったらそれに紐づくエラー内容を格納する。
class BasePolicy
attr_reader :error
def initialize(**_args)
@error = nil
end
def add(rule)
@rules << rule
end
def comply_with_all
failed_rule = @rules.find{ |rule| !rule.ok? }
return true if failed_rule.nil?
@error = failed_rule.error
false
end
private
# サブクラスでオーバーライド
def setup_rules; end
end
class XXXPolicy < BasePolicy
def initialize(arg)
@arg = arg
setup_rules
end
private
def setup_rules
add(XXXRule.new(arg))
add(YYYRule.new(arg))
end
end
各ルールに紐づくエラー内容は個別に定義しておく
class BaseRule
# MEMO: サブクラスでオーバーライドする
def ok?
false
end
# MEMO: サブクラスでオーバーライドする
def error
{
code: :internal_server_error,
status: :internal_server_error,
}
end
end
ルールの例
class XXXRule < BaseRule
def initialize(arg)
@arg = arg
end
def ok?
# 判定ロジック
end
def error
{
code: :bad_request,
status: :bad_request,
}
end
end
class YYYRule < BaseRule
def initialize(arg)
@arg = arg
end
def ok?
# 判定ロジック
end
def error
{
code: :not_found,
status: :not_found,
}
end
end
コントローラではこうなる
.
.
.
def check_xxx
policy = XXXPolicy.new(必要な引数渡す)
return if policy.comply_with_all
render_error_response(codes: [policy.error[:code]], status: policy.error[:status])
end
これでPolicyパターンで判定ロジックを集約しつつ、controllerをスリム化できた
Discussion