Class: Flows::Result Abstract
- Inherits:
-
Object
- Object
- Flows::Result
- Defined in:
- lib/flows/result.rb,
lib/flows/result/do.rb,
lib/flows/result/ok.rb,
lib/flows/result/err.rb,
lib/flows/result/errors.rb,
lib/flows/result/helpers.rb
Overview
Result Object is a way of presenting the result of a calculation. The result may be successful or failed.
For example, if you calculate expression a / b
:
- for
a = 6
andb = 2
result will be successful with data3
. - for
a = 6
andb = 0
result will be failed with data, for example,"Cannot divide by zero"
.
Examples of such approach may be found in other libraries and languages:
- Either Monad in Haskell.
- Result Type in Rust.
- Faraday gem has
Faraday::Response
object which contains data and status. - dry-rb Result Monad has
Dry::Monads::Result
.
So, why do you need Result Object?
Why not just return nil
on a failure or raise an error (like in the standard library)?
Here are several reasons:
- Raising errors and exceptions is a bad way
of handling errors.
Moreover, it is slow and looks like
goto
. However, it is still a good way to abort execution on an unexpected error. - Returning
nil
does not work when you have to deal with different types of errors or an error has some data payload. - Using specific Result Objects (like
Faraday::Response
) brings inconsistency - you have to learn how to deal with each new type of Result.
That's why Flows
should have Result Object implementation.
If any executable Flows entity will return Result Object with the same API -
composing your app components becomes trivial.
Result Objects should also be as fast and lightweight as possible.
Flows' implementation is inspired mainly by Rust Result Type and focused on following features:
- Use idiomatic Ruby: no methods named with first capital letter (
Name(1, 2)
), etc. - Use
case
and===
(case equality) for matching results and writing routing logic. - Provide helpers for convenient creation and matching of Result Objects (Helpers).
- Result Object may be successful (Ok) or failure (Err).
- Result Object has an #status (some symbol:
:saved
,:zero_division_error
). - Status usage is optional. Default statuses for successful and failure results are
:ok
and:err
. - Result may have metadata (#meta). Metadata is something unrelated to your business logic (execution time, for example, or some info about who created this result). This data must not be used in business logic, it's for a library code.
- Different accessors for successful and failure results - prevents treating failure results as successful and vice versa.
General Recommendations
Let's assume that you have some code returning Result Object.
- if an error happened and may be handled somehow - return failure result.
- if an error happened and cannot be handled - raise exception to abort execution.
- if you don't handle any errors for now - don't check result type and use #unwrap to access data. It will raise exception when called on a failure result.
Defined Under Namespace
Modules: Do, Helpers Classes: AccessError, Err, Error, Ok
Instance Attribute Summary collapse
-
#meta ⇒ Hash
readonly
Metadata, don't use it to store business data.
-
#status ⇒ Symbol
readonly
Status of Result Object, default is
:ok
for successful results and:err
for failure results.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
Results are equal if have same type and data.
-
#err? ⇒ Boolean
abstract
true
if result is failure. -
#error ⇒ Object
abstract
Result data.
-
#initialize ⇒ Result
constructor
Direct creation of this abstract class is forbidden.
-
#ok? ⇒ Boolean
abstract
true
if result is successful. -
#unwrap ⇒ Object
abstract
Result data.
Constructor Details
#initialize ⇒ Result
Direct creation of this abstract class is forbidden.
177 178 179 |
# File 'lib/flows/result.rb', line 177 def initialize(**) raise 'Use Flows::Result::Ok or Flows::Result::Err for build result objects' end |
Instance Attribute Details
#meta ⇒ Hash (readonly)
Returns metadata, don't use it to store business data.
172 173 174 |
# File 'lib/flows/result.rb', line 172 def @meta end |
#status ⇒ Symbol (readonly)
Returns status of Result Object, default is :ok
for successful results
and :err
for failure results.
169 170 171 |
# File 'lib/flows/result.rb', line 169 def status @status end |
Instance Method Details
#==(other) ⇒ Boolean
Results are equal if have same type and data.
Metadata is ignored in comparison.
186 187 188 189 190 |
# File 'lib/flows/result.rb', line 186 def ==(other) return false if self.class != other.class (status == other.status) && (data == other.send(:data)) end |
#err? ⇒ Boolean
Returns true
if result is failure.
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/flows/result.rb', line 166 class Result # @return [Symbol] status of Result Object, default is `:ok` for successful results # and `:err` for failure results. attr_reader :status # @return [Hash] metadata, don't use it to store business data attr_reader :meta # Direct creation of this abstract class is forbidden. # # @raise [StandardError] you will get an error def initialize(**) raise 'Use Flows::Result::Ok or Flows::Result::Err for build result objects' end # Results are equal if have same type and data. # # Metadata is ignored in comparison. # # @return [Boolean] def ==(other) return false if self.class != other.class (status == other.status) && (data == other.send(:data)) end private attr_accessor :data end |
#error ⇒ Object
Returns result data.
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/flows/result.rb', line 166 class Result # @return [Symbol] status of Result Object, default is `:ok` for successful results # and `:err` for failure results. attr_reader :status # @return [Hash] metadata, don't use it to store business data attr_reader :meta # Direct creation of this abstract class is forbidden. # # @raise [StandardError] you will get an error def initialize(**) raise 'Use Flows::Result::Ok or Flows::Result::Err for build result objects' end # Results are equal if have same type and data. # # Metadata is ignored in comparison. # # @return [Boolean] def ==(other) return false if self.class != other.class (status == other.status) && (data == other.send(:data)) end private attr_accessor :data end |
#ok? ⇒ Boolean
Returns true
if result is successful.
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/flows/result.rb', line 166 class Result # @return [Symbol] status of Result Object, default is `:ok` for successful results # and `:err` for failure results. attr_reader :status # @return [Hash] metadata, don't use it to store business data attr_reader :meta # Direct creation of this abstract class is forbidden. # # @raise [StandardError] you will get an error def initialize(**) raise 'Use Flows::Result::Ok or Flows::Result::Err for build result objects' end # Results are equal if have same type and data. # # Metadata is ignored in comparison. # # @return [Boolean] def ==(other) return false if self.class != other.class (status == other.status) && (data == other.send(:data)) end private attr_accessor :data end |
#unwrap ⇒ Object
Returns result data.
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/flows/result.rb', line 166 class Result # @return [Symbol] status of Result Object, default is `:ok` for successful results # and `:err` for failure results. attr_reader :status # @return [Hash] metadata, don't use it to store business data attr_reader :meta # Direct creation of this abstract class is forbidden. # # @raise [StandardError] you will get an error def initialize(**) raise 'Use Flows::Result::Ok or Flows::Result::Err for build result objects' end # Results are equal if have same type and data. # # Metadata is ignored in comparison. # # @return [Boolean] def ==(other) return false if self.class != other.class (status == other.status) && (data == other.send(:data)) end private attr_accessor :data end |