mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Implement ServiceResult#bind to allow chaining successful service call operations.
This commit is contained in:
@@ -245,6 +245,19 @@ class ServiceResult
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# Chains a subsequent service call if this result is successful, short-circuiting on failure.
|
||||
# Useful for composing several potentially-failing operations, returning the last successful
|
||||
# ServiceResult or the first failing one.
|
||||
#
|
||||
# @yield block to be called on success.
|
||||
# @yieldparam result [Object, nil] the result of the service call.
|
||||
# @yieldreturn [ServiceResult] the next ServiceResult in the chain.
|
||||
# @return [ServiceResult] the block's ServiceResult on success, or self on failure.
|
||||
def bind
|
||||
success? ? yield(result) : self
|
||||
end
|
||||
|
||||
##
|
||||
# Iterates exactly once, passing the result to the block, if the service call
|
||||
# succeeded.
|
||||
|
||||
@@ -244,4 +244,63 @@ RSpec.describe ServiceResult, type: :model do
|
||||
it { is_expected.to include(info: message) }
|
||||
end
|
||||
end
|
||||
|
||||
describe "#bind" do
|
||||
context "when successful" do
|
||||
let(:first) { described_class.success(result: 42) }
|
||||
|
||||
it "calls the block with the result and returns the block's ServiceResult" do
|
||||
returned = first.bind { |r| described_class.success(result: r * 2) }
|
||||
|
||||
expect(returned).to be_success
|
||||
expect(returned.result).to eq(84)
|
||||
end
|
||||
|
||||
it "returns the failure from the block when the block fails" do
|
||||
failure = described_class.failure(message: "step 2 failed")
|
||||
|
||||
returned = first.bind { failure }
|
||||
|
||||
expect(returned).to be(failure)
|
||||
expect(returned.message).to eq("step 2 failed")
|
||||
end
|
||||
end
|
||||
|
||||
context "when failed" do
|
||||
let(:first) { described_class.failure(message: "step 1 failed") }
|
||||
|
||||
it "returns self without calling the block" do
|
||||
expect { |b| first.bind(&b) }.not_to yield_control
|
||||
|
||||
expect(first.bind { described_class.success }).to be(first)
|
||||
end
|
||||
end
|
||||
|
||||
context "when chaining multiple calls" do
|
||||
it "returns the last success when all succeed" do
|
||||
result = described_class.success(result: 1)
|
||||
.bind { |r| described_class.success(result: r + 1) }
|
||||
.bind { |r| described_class.success(result: r + 1) }
|
||||
|
||||
expect(result).to be_success
|
||||
expect(result.result).to eq(3)
|
||||
end
|
||||
|
||||
it "short-circuits at the first failure" do
|
||||
second_called = false
|
||||
failure = described_class.failure(message: "failed")
|
||||
|
||||
result = described_class.success(result: 1)
|
||||
.bind { failure }
|
||||
.bind do
|
||||
second_called = true
|
||||
described_class.success
|
||||
end
|
||||
|
||||
expect(result).to be(failure)
|
||||
expect(result.message).to eq("failed")
|
||||
expect(second_called).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user