Pipeline & Steps
Every operation runs through a pipeline of wrapper steps. Understanding the pipeline is useful for debugging, and the use API lets you extend it with custom behavior.
Default pipeline
The default pipeline, from outermost to innermost:
result > lock > transaction > record > rescue > callbacks > performEach step wraps everything inside it. For example, transaction wraps record, rescue, callbacks, and perform – so all of those run inside a database transaction.
Inspecting the pipeline
CreateUser.pipeline.steps
# => [
# #<data name=:result, method=:_result_wrap>,
# #<data name=:lock, method=:_lock_wrap>,
# #<data name=:transaction, method=:_transaction_wrap>,
# #<data name=:record, method=:_record_wrap>,
# #<data name=:rescue, method=:_rescue_wrap>,
# #<data name=:callback, method=:_callback_wrap>
# ]Adding custom steps
Use use to register a module as a pipeline step:
module RateLimitWrapper
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def rate_limit(key, max:, period:)
set(:rate_limit, key: key, max: max, period: period)
end
end
def _rate_limit_wrap
key = self.class.settings_for(:rate_limit)[:key]
if RateLimiter.exceeded?(key)
error!(:rate_limited)
else
yield
end
end
end
class ApiOperation < Dex::Operation
use RateLimitWrapper
rate_limit "api", max: 100, period: 1.minute
def perform
# ...
end
endThe convention: your module provides a _name_wrap instance method that calls yield to proceed to the next step. Dexkit derives the step name from the module name (stripping Wrapper suffix, converting to snake_case).
Positioning steps
By default, new steps are added at the inner end of the pipeline (just before perform). You can control placement:
# At the outermost position (before everything)
use MyWrapper, at: :outer
# At the innermost position (closest to perform) – the default
use MyWrapper, at: :inner
# Before a specific step
use MyWrapper, before: :transaction
# After a specific step
use MyWrapper, after: :rescueExplicit naming
If your module name doesn't follow the XxxWrapper convention, or you want a different step name:
use MyModule, as: :rate_limit, wrap: :_my_custom_wrap_methodas:– the step name (defaults to derived from module name)wrap:– the wrap method name (defaults to_#{step_name}_wrap)
Removing steps
Pipeline steps can be removed by name:
class NoRecordOperation < Dex::Operation
pipeline.remove(:record)
endHow steps work
Each step is a method that receives a block (the rest of the pipeline). It must call yield to continue execution. If it doesn't yield, the inner steps – including perform – are never called.
def _my_step_wrap
# before logic
yield
# after logic
endThis is the same pattern as Rack middleware, Rails around callbacks, or any other onion-style architecture.