diff --git a/plugins/chat/app/services/service/base.rb b/plugins/chat/app/services/service/base.rb index f3902dab9e5..b21d03e055d 100644 --- a/plugins/chat/app/services/service/base.rb +++ b/plugins/chat/app/services/service/base.rb @@ -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 won’t 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. # diff --git a/plugins/chat/spec/lib/service_runner_spec.rb b/plugins/chat/spec/lib/service_runner_spec.rb index bbeb4df6b94..9afa8d39722 100644 --- a/plugins/chat/spec/lib/service_runner_spec.rb +++ b/plugins/chat/spec/lib/service_runner_spec.rb @@ -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 }