From d749227e87bfc5df96a5b2fbace7601a83351633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Guitaut?= Date: Tue, 20 Aug 2024 12:05:41 +0200 Subject: [PATCH] DEV: Allow using an AR relation as a model in services This patch allows using an AR relation as a model in services without fetching associated records. It will just check if the relation is empty or not. In the former case, the execution will stop at that point, as expected. --- {plugins/chat/app => app}/jobs/service_job.rb | 0 app/services/service/base.rb | 6 ++- lib/service_runner.rb | 4 +- .../spec => spec}/lib/service_runner_spec.rb | 44 ++++++++++++++++++- 4 files changed, 50 insertions(+), 4 deletions(-) rename {plugins/chat/app => app}/jobs/service_job.rb (100%) rename {plugins/chat/spec => spec}/lib/service_runner_spec.rb (90%) diff --git a/plugins/chat/app/jobs/service_job.rb b/app/jobs/service_job.rb similarity index 100% rename from plugins/chat/app/jobs/service_job.rb rename to app/jobs/service_job.rb diff --git a/app/services/service/base.rb b/app/services/service/base.rb index 531169f44cd..08f60c6a487 100644 --- a/app/services/service/base.rb +++ b/app/services/service/base.rb @@ -139,13 +139,15 @@ module Service def call(instance, context) context[name] = super - raise ArgumentError, "Model not found" if !optional && context[name].blank? + if !optional && (!context[name] || context[name].try(:empty?)) + raise ArgumentError, "Model not found" + end if context[name].try(:invalid?) context[result_key].fail(invalid: true) context.fail! end rescue ArgumentError => exception - context[result_key].fail(exception: exception) + context[result_key].fail(exception: exception, not_found: true) context.fail! end end diff --git a/lib/service_runner.rb b/lib/service_runner.rb index 7dbec6e7a67..05c9f507709 100644 --- a/lib/service_runner.rb +++ b/lib/service_runner.rb @@ -80,7 +80,9 @@ class ServiceRunner default_name: "default", }, on_model_not_found: { - condition: ->(name = "model") { failure_for?("result.model.#{name}") && result[name].blank? }, + condition: ->(name = "model") do + failure_for?("result.model.#{name}") && result["result.model.#{name}"].not_found + end, key: %w[result model], default_name: "model", }, diff --git a/plugins/chat/spec/lib/service_runner_spec.rb b/spec/lib/service_runner_spec.rb similarity index 90% rename from plugins/chat/spec/lib/service_runner_spec.rb rename to spec/lib/service_runner_spec.rb index 9afa8d39722..bfd6eb653c4 100644 --- a/plugins/chat/spec/lib/service_runner_spec.rb +++ b/spec/lib/service_runner_spec.rb @@ -136,6 +136,18 @@ RSpec.describe ServiceRunner do end end + class RelationModelService + include Service::Base + + model :fake_model + + private + + def fetch_fake_model + User.where(admin: false) + end + end + describe ".call(service, &block)" do subject(:runner) { described_class.call(service, object, &actions_block) } @@ -145,7 +157,9 @@ RSpec.describe ServiceRunner do let(:actions) { "proc {}" } let(:object) do Class - .new(Chat::ApiController) do + .new(ApplicationController) do + include WithServiceHelper + def request OpenStruct.new end @@ -352,6 +366,34 @@ RSpec.describe ServiceRunner do end end end + + context "when fetching an ActiveRecord relation" do + let(:service) { RelationModelService } + + context "when the service does not fail" do + before { Fabricate(:user) } + + it "does not run the provided block" do + expect(runner).not_to eq :no_model + end + + it "does not fetch records from the relation" do + runner + expect(result[:fake_model]).not_to be_loaded + end + end + + context "when the service fails" do + it "runs the provided block" do + expect(runner).to eq :no_model + end + + it "does not fetch records from the relation" do + runner + expect(result[:fake_model]).not_to be_loaded + end + end + end end context "when using the on_model_errors action" do