WFlow
WFlow aims to help on designing workflows based on Single Responsibility Principle. WFlow proposes to achieve this by providing tools to help compose those classes into a workflow.
Word of appreciation for usecasing, interactor and rest_my_case that served as inspiration for this gem.
Dependencies
Tested with:
- ruby 2.2.2, 2.2.1, 2.2.0, 2.1.1, 2.0.0
Installation
Add this line to your application's Gemfile:
gem 'w_flow'
And then execute:
$ bundle
Or install it yourself as:
$ gem install w_flow
Usage
In its most simplest form a Process would look like this:
# Process to retrive a user from the database given a user_id
class FindUser
# include module Process
include WFlow::Process
# perform is where you'll execute your business logic
def perform
# flow is an object present in a Process, and it is how you retrieve data
# from the current flow, in this case the input user_id
user_id = flow.data.user_id
# when you want to output a value from the Process you set it in data
flow.data.user = User.find(user_id)
end
end
And you invoke it like this:
# run will returns a report object
report = FindUser.run(user_id: 10)
# this report object will contain the output from the Process
report.data.user
This and any other Process can be used to compose a workflow, so lets try that:
class SendWelcomeEmail
include WFlow::Process
# use previously created Process to find the user
execute FindUser
def perform
# code to send email to user
end
end
report = SendWelcomeEmail.run(user_id: 10)
Processes passed to execute will be called before the perform method. You can have as any execute as you want and as many Processes (and method nomes and Procs) in a execute. This means that when you run SendWelcomeEmail process, it will first execute FindUser which will set the user under flow.data, and then you can use that user to get the email address for where to send the email.
So far so good, but lets go back to FindUser. Looking at it, we are currently not accounting for errors cases, like what should happen when we can't find an user, or when the connection to the database fails? This depends on what you want to do, but for this example we'll raise a flow failure, and we'll also simplify the code a bit using data helpers:
class FindUser
include WFlow::Process
# helper methods to access attributes under flow.data
data_reader :user_id
data_accessor :user
def perform
self.user = User.find(user_id)
rescue
# you can pass whatever you want to the failure! method (or even call it without arguments)
# passed value will be available in returned report under failure_log
flow.failure!('unable to find user')
end
end
report = FindUser.run(user_id: 10)
# check if success
unless report.success? # there's also a report.failure?
# failure_log is an array that contains all the objects passed to failure!
report.failure_log.each do |log|
puts log
end
end
Invoking failure! will interrupt a workflow immediatly. This means that if you run SendWelcomeEmail for a non existing user it will never run the code under perform (which is a good thing, since there's no one to send the email to). But what if we want to do something more even in case of failure? You can pass a handler for that:
class SendWelcomeEmail
include WFlow::Process
attr_writer :admin_email
# you can pass a name or a proc as a failure handler, which will be called if one
# of the Processes in execute chain raises a flow failure
execute FindUser, failure: :on_failure
# we'll use an if handler (there's also an unless handler), which allows us to control if a execute chain
# should be executed or not
execute :compose_email, SendMessageToAdmin, -> { flow.failure! }, if: -> { @no_user_found }
def perform
# ...
end
protected
# failure handler, return false to cancel failure, or true to let Process fail
def on_failure
@no_user_found = true
false
end
def compose_email
self.admin_email = "we were unable to find user :("
end
end
Wow it suddenly become complex, but it reflects a more realistic situation. So what's going on? First we try to find the user, which will raise a flow failure if no user is found. In this case we want to inform the admin that something went wrong, so instead of allowing the flow to be interrupted right away, we return false in the failure handler to cancel failure. After that, the second execute chain will be executed, because @no_user_found is set to true. This execution chain will invoke the method compose_email, SendMessageToAdmin, and call proc that reraises failure.
This is some of the features of WFlow, please check wiki for more details.
Contributing
- Fork it ( https://github.com/junhanamaki/w_flow/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request