Class: Railsmith::Pipeline

Inherits:
Object
  • Object
show all
Defined in:
lib/railsmith/pipeline.rb,
lib/railsmith/pipeline/errors.rb,
lib/railsmith/pipeline/runner.rb,
lib/railsmith/pipeline/step_definition.rb

Overview

Sequential service composition with fail-fast semantics.

A pipeline declares an ordered list of steps, each backed by a Railsmith::BaseService subclass. On each call, the Runner walks the steps in order, merging the previous step’s result.value (when it is a Hash) into the accumulated params so the next step receives everything seen so far. On the first failure the pipeline halts and returns that failure Result, annotated with :pipeline_name and :pipeline_step in meta.

Basic usage

class CheckoutPipeline < Railsmith::Pipeline
  step :validate_cart,      service: CartService,         action: :validate
  step :reserve_inventory,  service: InventoryService,    action: :reserve,
                            rollback: :unreserve
  step :charge_payment,     service: PaymentService,      action: :charge,
                            inputs: { amount: :cart_total }, rollback: :refund
  step :send_confirmation,  service: NotificationService, action: :send_receipt
end

result = CheckoutPipeline.call(params: { cart_id: 42, user_id: 7 })
result.success?                    # => true
result.meta[:pipeline_name]        # => "CheckoutPipeline" (on failure only)

Param forwarding

Each step receives accumulated_params, which starts as the original params and grows as each step’s Hash result.value is merged in. Use inputs: to rename keys before they reach a specific step’s service:

step :charge, service: PaymentService, action: :charge,
     inputs: { amount: :cart_total }

This renames the :cart_total key to :amount for PaymentService only; subsequent steps still see :cart_total in accumulated params.

Instrumentation

Two events are emitted per run:

"pipeline.step.railsmith"  — fired for each step; payload includes
                             :pipeline, :step, :status, :duration
"pipeline.railsmith"       — fired once on completion; payload includes
                             :pipeline, :status, :duration

Defined Under Namespace

Classes: GuardNotFoundError, ParamCollisionError, ParamMappingError, Runner, StepDefinition

Class Method Summary collapse

Class Method Details

.call(params: {}, context: UNSET_CONTEXT) ⇒ Object

Run the pipeline and return a Result.

Parameters:

  • params (Hash) (defaults to: {})

    initial params forwarded to the first step

  • context (Hash, Railsmith::Context, nil) (defaults to: UNSET_CONTEXT)


114
115
116
117
118
119
120
121
122
# File 'lib/railsmith/pipeline.rb', line 114

def call(params: {}, context: UNSET_CONTEXT)
  resolved =
    if context.equal?(UNSET_CONTEXT)
      Context.current || Context.build(nil)
    else
      Context.build(context)
    end
  Runner.new(pipeline_class: self, params: params, context: resolved).run
end

.call!(params: {}, context: UNSET_CONTEXT) ⇒ Object

Run the pipeline; raise Railsmith::Failure on the first step failure.

Raises:



125
126
127
128
129
130
# File 'lib/railsmith/pipeline.rb', line 125

def call!(params: {}, context: UNSET_CONTEXT)
  result = call(params: params, context: context)
  raise Railsmith::Failure, result if result.failure?

  result
end

.guard(name, &block) ⇒ Object

Register a named guard predicate for use in step if:/unless: options.

guard :has_coupon? do |params, ctx|
  params.key?(:coupon_code)
end

step :apply_coupon, service: CouponService, action: :apply, if: :has_coupon?

The block receives (accumulated_params, context) and must return a truthy/falsy value.

Raises:

  • (ArgumentError)


89
90
91
92
93
# File 'lib/railsmith/pipeline.rb', line 89

def guard(name, &block)
  raise ArgumentError, "guard block is required" if block.nil?

  guards[name.to_sym] = block
end

.guardsObject

Registered named guard predicates for this pipeline class.



96
97
98
# File 'lib/railsmith/pipeline.rb', line 96

def guards
  @guards ||= {}
end

.inherited(subclass) ⇒ Object

Subclasses start with private copies of the parent’s step list and guard registry so additions on the subclass do not leak back upward.



134
135
136
137
138
# File 'lib/railsmith/pipeline.rb', line 134

def inherited(subclass)
  super
  subclass.instance_variable_set(:@step_definitions, step_definitions.dup)
  subclass.instance_variable_set(:@guards, guards.dup)
end

.pipeline_nameObject

Human-readable name used in instrumentation payloads.



106
107
108
# File 'lib/railsmith/pipeline.rb', line 106

def pipeline_name
  name || "AnonymousPipeline"
end

.step(name, service:, action:, inputs: nil, rollback: nil, **options) ⇒ Object

Declare a step in execution order.

Parameters:

  • name (Symbol)

    identifier used in events and error meta

  • service (Class)

    a Railsmith::BaseService subclass

  • action (Symbol)

    action forwarded to service.call(action:)

  • inputs (Hash, nil) (defaults to: nil)

    optional { target_key => source_key } renames

  • rollback (Symbol, Proc, nil) (defaults to: nil)

    Compensation handler invoked (in reverse step order) when a later step fails. Symbol — action name on the same service. Proc — called as proc.call(step_result, ctx).

  • if (Symbol, Proc, nil)

    Guard condition: step is executed only when the proc/guard returns truthy. Proc form: ->(params, ctx) { … }; Symbol form: a named guard declared via guard.

  • unless (Symbol, Proc, nil)

    Inverse guard: step is skipped when the proc/guard returns truthy.

  • on_failure_continue (Boolean)

    When true, a failure from this step does not halt the pipeline; subsequent steps run as if the step was skipped. The failed step is not rolled back.



74
75
76
77
78
# File 'lib/railsmith/pipeline.rb', line 74

def step(name, service:, action:, inputs: nil, rollback: nil, **options)
  condition, polarity = extract_step_condition(options)
  attrs = { name: name, service: service, action: action, inputs: inputs, rollback: rollback }
  step_definitions << StepDefinition.new(**step_definition_attributes(attrs, condition, polarity, options))
end

.step_definitionsObject

Ordered list of StepDefinition records for this pipeline class.



101
102
103
# File 'lib/railsmith/pipeline.rb', line 101

def step_definitions
  @step_definitions ||= []
end