DEV: Fix policy classes delegating their `#call` method in services

There’s currently a bug when using a dedicated class as a policy in
services: if that class delegates its `#call` method (to an underlying
strategy object for example), then an error will be raised saying steps
aren’t allowed to provide default parameters.

This should not happen, and this patch fixes that issue.
This commit is contained in:
Loïc Guitaut 2024-12-13 15:28:35 +01:00 committed by Loïc Guitaut
parent 9812407f76
commit 133a648d9b
2 changed files with 34 additions and 8 deletions

View File

@ -91,21 +91,17 @@ module Service
# @!visibility private
module StepsHelpers
def model(name = :model, step_name = :"fetch_#{name}", optional: false)
steps << ModelStep.new(name, step_name, optional: optional)
steps << ModelStep.new(name, step_name, optional:)
end
def params(name = :default, default_values_from: nil, &block)
contract_class = Class.new(Service::ContractBase).tap { _1.class_eval(&block) }
const_set("#{name.to_s.classify.sub("Default", "")}Contract", contract_class)
steps << ContractStep.new(
name,
class_name: contract_class,
default_values_from: default_values_from,
)
steps << ContractStep.new(name, class_name: contract_class, default_values_from:)
end
def policy(name = :default, class_name: nil)
steps << PolicyStep.new(name, class_name: class_name)
steps << PolicyStep.new(name, class_name:)
end
def step(name)
@ -152,7 +148,7 @@ module Service
def run_step
object = class_name&.new(context)
method = object&.method(:call) || instance.method(method_name)
if method.parameters.any? { _1[0] != :keyreq }
if !object && method.parameters.any? { _1[0] != :keyreq }
raise "In #{type} '#{name}': default values in step implementations are not allowed. Maybe they could be defined in a params or options block?"
end
args = context.slice(*method.parameters.select { _1[0] == :keyreq }.map(&:last))

View File

@ -38,6 +38,36 @@ RSpec.describe Service do
expect { service_class.call }.to raise_error(/In policy 'my_policy': default values/)
end
end
context "when providing a class which delegates its `#call` method" do
before do
service_class.class_eval do
class MyPolicy < Service::PolicyBase
class MyStrategy
def call
end
def reason
end
end
attr_reader :strategy
delegate :call, :reason, to: :strategy
def initialize(*)
@strategy = MyStrategy.new
end
end
policy :my_policy, class_name: MyPolicy
end
end
it "does not raise an error" do
expect { service_class.call }.not_to raise_error
end
end
end
describe "Generic step" do