Flow
Installation
Add this line to your application's Gemfile:
gem "flow"
Then, in your project directory:
$ bundle install
$ rails generate flow:install
What is Flow?
Flow is a SOLID implementation of the Command Pattern for Ruby on Rails.
Flows allow you to encapsulate your application's business logic into a set of extensible and reusable objects.
Quickstart Example
Install Flow to your Rails project:
$ rails generate flow:install
Then define State
, Operation
(s), and Flow
objects.
State
A State
object defines data that is to be read or written in Operation
objects throughout the Flow
. There are several types of data that can be defined, such as argument
, option
, and output
.
$ rails generate flow:state Charge
# app/states/charge_state.rb
class ChargeState < ApplicationState
# @!attribute [r]
# Order hash, readonly, required
argument :order
# @!attribute [r]
# User model instance readonly, required
argument :user
# @!attribute [r]
# PaymentMethod model instance readonly, optional
option :payment_method
# @!attribute [rw]
# Charge model instance readwrite, required
output :charge
end
Operations
Operation
objects execute some procedure defined in a #behavior
method and can read and write to State
data via defined accessor methods.
$ rails generate flow:operation CreateCharge
# app/operations/create_charge.rb
class CreateCharge < ApplicationOperation
# @!attribute [r]
# Order hash, readonly
state_reader :order
# @!attribute [r]
# User model instance readonly
state_reader :user
# @!attribute [r]
# PaymentMethod model instance readonly
state_reader :payment_method
# @!attribute [rw]
# Charge model instance readwrite
state_writer :charge
def behavior
state.charge = Charge.create(payment_method: payment_method, order: order, user: user)
end
private
def payment_method
payment_method.present? ? payment_method : user.default_payment_method
end
end
Use failure methods when an Operation
and Flow
should fail and no longer run:
$ rails generate flow:operation SubmitCharge
# app/operations/submit_charge.rb
class SubmitCharge < ApplicationOperation
# @!method charge_unsuccessful_failure!(data = {})
# Raises, stops the Operation and Flow, takes unstructured data hash
failure :charge_unsuccessful
# @!attribute [rw]
# Charge model instance read only
state_reader :charge
def behavior
charge_unsuccessful_failure!(response_body: response.body) unless success?
charge.update(success: true)
end
private
def success?
response.body.success == "true"
end
def response
PaymentProcessorClient.submit_charge(charge)
end
memoize :response
end
Flow
A Flow
object is composed of one or more ordered Operation
s. Changes to the state will persist from one Operation
to the next:
$ rails generate flow Charge
# app/flow/charge_flow.rb
class ChargeFlow < ApplicationFlow
operations CreateCharge,
SubmitCharge
end
Usage
Trigger the Flow
in your code with State
inputs:
flow_input = {
order: order,
user: current_user,
payment_method: visa_credit_card,
}
flow = ChargeFlow.trigger(flow_input)
Arguments defined on State
are required when triggering a Flow
, options are optional:
> ChargeFlow.trigger({})
ArgumentError: Missing arguments: order, user
State output can be accessed from the flow instance:
> flow = ChargeFlow.trigger(flow_input)
> flow.state.charge
=> #<Charge:0x00007fd5c5cda080 ... >
Success of the triggered Flow
can be determined with these methods:
> flow.success?
=> true
> flow.failed?
=> false
If the Flow
fails you can see the failures on the instance:
# some flow that results in a failure...
> flow = ChargeFlow.trigger(flow_input)
> flow.success?
=> false
# get the failure
> flow.operation_failure.problem
=> :charge_unsuccessful
# access unstructured hash passed into failure method
> flow.operation_failure.details.response_body
=> { some_response_body_here: ... }
Wiki
Learn more with our wiki Getting Started page.
You also can download wiki to have offline access. Just simply do:
git clone git@github.com:RubyAfterAll/flow.wiki.git
License
The gem is available as open source under the terms of the MIT License.