DEV: Allow chat services to have optional models

This is extracted from #22390.

This patch adds a new `optional` option to the `model` step. This
means if an optional model returns something blank (`nil` or an empty
collection) then the service won’t fail and will execute the next step.
However if a model is properly returned, the step will try to check if
it is valid or not (if it responds to `#invalid?`). If the model isn’t
valid, then the step will fail (so no change here).
This commit is contained in:
Loïc Guitaut 2023-07-06 11:33:41 +02:00 committed by Loïc Guitaut
parent c90e399003
commit 050828d1de
2 changed files with 32 additions and 4 deletions

View File

@ -74,8 +74,8 @@ module Service
# Internal module to define available steps as DSL
# @!visibility private
module StepsHelpers
def model(name = :model, step_name = :"fetch_#{name}")
steps << ModelStep.new(name, step_name)
def model(name = :model, step_name = :"fetch_#{name}", optional: false)
steps << ModelStep.new(name, step_name, optional: optional)
end
def contract(name = :default, class_name: self::Contract, default_values_from: nil)
@ -131,9 +131,16 @@ module Service
# @!visibility private
class ModelStep < Step
attr_reader :optional
def initialize(name, method_name = name, class_name: nil, optional: nil)
super(name, method_name, class_name: class_name)
@optional = optional.present?
end
def call(instance, context)
context[name] = super
raise ArgumentError, "Model not found" if context[name].blank?
raise ArgumentError, "Model not found" if !optional && context[name].blank?
if context[name].try(:invalid?)
context[result_key].fail(invalid: true)
context.fail!
@ -232,9 +239,10 @@ module Service
end
# @!scope class
# @!method model(name = :model, step_name = :"fetch_#{name}")
# @!method model(name = :model, step_name = :"fetch_#{name}", optional: false)
# @param name [Symbol] name of the model
# @param step_name [Symbol] name of the method to call for this step
# @param optional [Boolean] if +true+, then the step wont fail if its return value is falsy.
# Evaluates arbitrary code to build or fetch a model (typically from the
# DB). If the step returns a falsy value, then the step will fail.
#

View File

@ -64,6 +64,18 @@ RSpec.describe ServiceRunner do
end
end
class FailureWithOptionalModelService
include Service::Base
model :fake_model, optional: true
private
def fetch_fake_model
nil
end
end
class FailureWithModelErrorsService
include Service::Base
@ -284,6 +296,14 @@ RSpec.describe ServiceRunner do
BLOCK
context "when fetching a single model" do
context "when the service uses an optional model" do
let(:service) { FailureWithOptionalModelService }
it "does not run the provided block" do
expect(runner).not_to eq :no_model
end
end
context "when the service fails without a model" do
let(:service) { FailureWithModelService }