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 Operations. 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 [email protected]:Freshly/flow.wiki.git
License
The gem is available as open source under the terms of the MIT License.