Project

flow

0.0
No release in over a year
Tired of kitchen sink services, god-objects, and fat-everything? So were we. Get in the flow.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 3.1.13
>= 3.7.0
~> 0.16
~> 1.3
>= 2.0.1
~> 13.0
>= 0.25.7, < 1.0
>= 0.21.0, < 1.0
>= 5.2.1

Runtime

>= 5.2.1
>= 0.2.0, < 1.0
>= 0.2.0, < 1.0
>= 0.2.0, < 1.0
>= 0.2.0, < 1.0
>= 0.26.0, < 1.0
 Project Readme

Flow

Gem Version Build Status Maintainability Test Coverage

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 git@github.com:RubyAfterAll/flow.wiki.git

License

The gem is available as open source under the terms of the MIT License.