Module: FlowOrganizer
- Defined in:
- lib/flow_organizer.rb,
lib/flow_organizer/version.rb
Overview
FlowOrganizer passes the result of a callable to another callable (as long as the result is successfull).
It is mostly useful when you need to execute a series of operations ressembling a pipeline.
You might alredy be familiar with some solutions that deal with this (Promises, Railway Programming, Pipe operators): ‘FlowOrganizer` is a flavor of functional interactor.
### Introduction Describing a list of operations often leads to code that is difficult to follow or nested requires a lot of nesting. For instance: “‘ruby fire_user_created_event(persist_user(validate_password(validate_email({ email: email, password: password }))))
# or
valid_email? = validate_email(email) valid_password? = validate_password(password) if valid_email? && valid_password?
user = persist_user(email: email, password: password)
if user
fire_user_created_event(user: user)
end
end “‘
With ‘FlowOrganizer`, this is expressed as: “`ruby FlowOrganizer.call(
list: [
[:alias, :validate_email],
[:alias, :validate_password],
[:alias, :persist_user],
[:alias, :fire_user_created_event],
],
ctx: {
email: '',
password: '',
},
) “‘
#### Context An organizer uses a context. The context contains everything the set of operations need to work. When an operation is called, it can affect the context.
#### Callable A callable is expected to return a result tupple of the following format: “‘ruby
- :ok
-
|| [:ok, context_update] || [:halt] || [:halt, context_update] || [:error] || [:error, context_update]
“‘
Defined Under Namespace
Modules: Callable, Context Classes: Error
Constant Summary collapse
- VERSION =
'1.0.0'
Class Attribute Summary collapse
-
.exception_reporter ⇒ Object
Optional callback invoked when a callable raises.
Class Method Summary collapse
-
.call(list:, list_error: nil, ctx: nil, raise_exception: false) ⇒ Object
Run a ‘list` of `operations` (callables) in order.
- .call_list(list, status, ctx, raise_exception) ⇒ Object
-
.sanitize_errors(result) ⇒ Object
private
Sanitizes returned errors, if any.
Class Attribute Details
.exception_reporter ⇒ Object
Optional callback invoked when a callable raises. Signature: ‘->(exception:) { … }`. Wire this to Sentry/Honeybadger/etc. The default is no-op.
57 58 59 |
# File 'lib/flow_organizer.rb', line 57 def exception_reporter @exception_reporter end |
Class Method Details
.call(list:, list_error: nil, ctx: nil, raise_exception: false) ⇒ Object
Run a ‘list` of `operations` (callables) in order.
Each results update the initial ‘ctx` which is then sent to the next operation.
An ‘operation` needs to be a callable, but it can be resolved from other format (see `#to_callable`)
NOTE: Every operation is expected to return a tupple of the format ‘[:ok]` or `[:error]` with an optional context update (`[:ok, { new_ctx_key: ’value’ }]‘, `[:errors, { errors: [{ detail: ’Error explaination’ }], }]‘). If an `:error` tupple is returned, the next operations are canceled and `call` will return.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/flow_organizer.rb', line 71 def self.call(list:, list_error: nil, ctx: nil, raise_exception: false) ctx = (ctx || {}).dup list_error = [] unless list_error.is_a?(Array) status, ctx = call_list(list, :ok, ctx, raise_exception) # If there is an error on the "success" track (list), switch to the "error" track, (list_error) if status == :error status, ctx = call_list(list_error, status, ctx, raise_exception) end status = :ok if status == :halt [status, ctx] end |
.call_list(list, status, ctx, raise_exception) ⇒ Object
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/flow_organizer.rb', line 87 def self.call_list(list, status, ctx, raise_exception) list.each do |el| # Inline fast path for raw callables — skip the resolver allocation. if el.respond_to?(:call) callable = el else _, resolved = FlowOrganizer::Callable.resolve(target: el) callable = resolved[:callable] end # Generate arguments compatible with what the callable expects local_ctx = FlowOrganizer::Context.generate_callable_ctx(callable: callable, ctx: ctx) # Skip the empty `**` splat when the callable takes no kwargs. result = local_ctx.nil? ? callable.call : callable.call(**local_ctx) status, local_ctx = result # `sanitize_errors` is a no-op on `:ok` — only call when it might do work. if status == :error result = sanitize_errors(result) _, local_ctx = result end # Mutate ctx in place (we own it — `call` dup'd it) and skip when nothing to merge. if local_ctx && !local_ctx.empty? FlowOrganizer::Context.update_context!(ctx: ctx, local_ctx: local_ctx) end # Stop execution status is not `:ok` break if status == :error || status == :halt rescue StandardError => e status = :error ctx[:error] = e if raise_exception raise e else exception_reporter&.call(exception: e) end break end [status, ctx] end |
.sanitize_errors(result) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Enable simpler error return format from an organized callable.
Sanitizes returned errors, if any.
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/flow_organizer.rb', line 144 def self.sanitize_errors(result) status, ctx = result return result if status != :error case ctx when String ctx = { errors: [{ title: ctx }] } when Hash if !ctx[:errors] if ctx[:title] || ctx[:detail] || ctx[:code] ctx = { errors: [ctx] } elsif ctx[:error] ctx = { errors: [ctx[:error]] } end end when Array ctx = { errors: ctx.map { |el| (el.is_a?(String)) ? { title: el } : el } } end [status, ctx] end |