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.
This commit is contained in:
Loïc Guitaut 2024-08-20 12:05:41 +02:00 committed by Loïc Guitaut
parent db6eff7be9
commit 0636855706
4 changed files with 50 additions and 4 deletions

View File

@ -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

View File

@ -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",
},

View File

@ -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