Class: SenroUsecaser::Result

Inherits:
Object
  • Object
show all
Defined in:
lib/senro_usecaser/result.rb

Overview

Represents the result of a UseCase execution

Result is a generic type that holds either a success value or an array of errors. Use Result.success and Result.failure class methods to create instances.

Examples:

Success case

result = SenroUsecaser::Result.success(user)
result.success? # => true
result.value    # => user

Failure case

result = SenroUsecaser::Result.failure(
  SenroUsecaser::Error.new(code: :not_found, message: "User not found")
)
result.failure? # => true
result.errors   # => [#<SenroUsecaser::Error ...>]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(value: nil, errors: []) ⇒ Result

: (?value: T?, ?errors: Array) -> void



90
91
92
93
94
# File 'lib/senro_usecaser/result.rb', line 90

def initialize(value: nil, errors: [])
  @value = value
  @errors = errors.freeze
  freeze
end

Instance Attribute Details

#errors ⇒ Object (readonly)

Returns the value of attribute errors.



32
33
34
# File 'lib/senro_usecaser/result.rb', line 32

def errors
  @errors
end

#value ⇒ Object (readonly)

Returns the value of attribute value.



30
31
32
# File 'lib/senro_usecaser/result.rb', line 30

def value
  @value
end

Class Method Details

.capture(*exception_classes, code: :exception, &block) ⇒ Object

Executes a block and captures any exception as a failure Result

: [T] (*Class, ?code: Symbol) { () -> T } -> Result

Examples:

result = Result.capture { User.find(id) }
# If User.find raises, result is a failure with the exception
# If User.find succeeds, result is a success with the return value

With specific exception classes

result = Result.capture(ActiveRecord::RecordNotFound, code: :not_found) do
  User.find(id)
end


79
80
81
82
83
84
85
86
87
# File 'lib/senro_usecaser/result.rb', line 79

def self.capture(*exception_classes, code: :exception, &block)
  raise ArgumentError, "Block is required" unless block

  exception_classes = [StandardError] if exception_classes.empty?
  value = block.call
  success(value)
rescue *exception_classes => e
  from_exception(e, code: code)
end

.failure(*errors) ⇒ Object

Creates a failure Result with the given errors

: (*Error) -> Result

Raises:

  • (ArgumentError)


44
45
46
47
48
49
# File 'lib/senro_usecaser/result.rb', line 44

def self.failure(*errors)
  errors = errors.flatten
  raise ArgumentError, "At least one error is required for failure" if errors.empty?

  new(value: nil, errors: errors)
end

.from_exception(exception, code: :exception) ⇒ Object

Creates a failure Result from an exception

: (Exception, ?code: Symbol) -> Result

Examples:

begin
  # some code that raises
rescue => e
  Result.from_exception(e)
end


61
62
63
64
# File 'lib/senro_usecaser/result.rb', line 61

def self.from_exception(exception, code: :exception)
  error = Error.from_exception(exception, code: code)
  failure(error)
end

.success(value) ⇒ Object

Creates a success Result with the given value

: [T] (T) -> Result



37
38
39
# File 'lib/senro_usecaser/result.rb', line 37

def self.success(value)
  new(value: value, errors: [])
end

Instance Method Details

#==(other) ⇒ Object

: (Result) -> bool



163
164
165
166
167
168
169
# File 'lib/senro_usecaser/result.rb', line 163

def ==(other)
  return false unless other.is_a?(Result)

  # @type var v: untyped
  v = @value
  v == other.value && errors == other.errors
end

#and_then(&block) ⇒ Object

Applies a block to the value if success, returns failure with same errors if failure The block should return a Result

: [U] () { (T) -> Result } -> Result



145
146
147
148
149
150
151
# File 'lib/senro_usecaser/result.rb', line 145

def and_then(&block)
  return Result.new(value: nil, errors: errors) if failure?

  # @type var v: untyped
  v = @value
  block.call(v)
end

#failure? ⇒ Boolean

Returns true if the result is a failure

: () -> bool

Returns:

  • (Boolean)


106
107
108
# File 'lib/senro_usecaser/result.rb', line 106

def failure?
  !success?
end

#inspect ⇒ Object

: () -> String



172
173
174
175
176
177
178
179
180
# File 'lib/senro_usecaser/result.rb', line 172

def inspect
  if success?
    # @type var v: untyped
    v = @value
    "#<#{self.class.name} success value=#{v.inspect}>"
  else
    "#<#{self.class.name} failure errors=#{errors.inspect}>"
  end
end

#map(&block) ⇒ Object

Applies a block to the value if success, returns failure with same errors if failure

: [U] () { (T) -> U } -> Result



133
134
135
136
137
138
139
# File 'lib/senro_usecaser/result.rb', line 133

def map(&block)
  return Result.new(value: nil, errors: errors) if failure?

  # @type var v: untyped
  v = @value
  Result.success(block.call(v))
end

#or_else(&block) ⇒ Object

Applies a block to the errors if failure, returns self if success

: () { (Array) -> Result } -> Result



156
157
158
159
160
# File 'lib/senro_usecaser/result.rb', line 156

def or_else(&block)
  return self if success?

  block.call(errors)
end

#success? ⇒ Boolean

Returns true if the result is a success

: () -> bool

Returns:

  • (Boolean)


99
100
101
# File 'lib/senro_usecaser/result.rb', line 99

def success?
  errors.empty?
end

#value! ⇒ Object

Returns the value if success, otherwise raises an error

: () -> T



113
114
115
116
117
# File 'lib/senro_usecaser/result.rb', line 113

def value! # steep:ignore MethodBodyTypeMismatch
  raise "Cannot unwrap value from a failure result" if failure?

  @value
end

#value_or(default) ⇒ Object

Returns the value if success, otherwise returns the given default

: [U] (U) -> (T | U)



122
123
124
125
126
127
128
# File 'lib/senro_usecaser/result.rb', line 122

def value_or(default) # steep:ignore MethodBodyTypeMismatch
  if success?
    @value
  else
    default
  end
end