# Seam Simple workflows in Ruby. ## Usage Seam is meant for situations where you want to take some entity (user, order, ec.) through a long-running process that is comprised of multiple steps. For example, if you want every new user a "hello" email after signup, then wait a few days, and then send a "gone so soon?" email if they haven't signed in again. This gem provides some simple tools for building and executing this process. It provides a way to define the process, break it up into separate components, and then send entities through the process. ####Define a workflow#### To start, define a workflow. ```` flow = Seam::Flow.new flow.send_order_to_warehouse flow.wait_for_order_to_be_shipped wait_up_to: 7.days flow.send_shipping_email email_template: 'shipping_7' flow.mark_order_as_fulfilled ```` A flow will convert any method call you make into a step that has to be completed. You can also pass a hash to the method, which will be saved for later. ```` flow.wait_for_order_to_be_shipped wait_up_to: 7.days ```` ####Starting an instance of the flow#### Starting an instance of the flow is done with "start": ```` flow.start order_id: '1234' ```` An instance of this effort was created and saved in whatever persistence is being used (in-memory by default). This effort will start at the first step (send_order_to_warehouse) and then progress through the steps as they are completed. "start" also returns the effort that was created, and it will look like this: ```` [{"name"=>"send_order_to_warehouse", "type"=>"do", "arguments"=>{}}, {"name"=>"wait_for_order_to_be_shipped", "type"=>"do", "arguments"=>{}}, {"name"=>"send_shipping_email", "type"=>"do", "arguments"=>{}}, {"name"=>"mark_order_as_fulfilled", "type"=>"do", "arguments"=>{}}]}, @data={"order_id"=>"1234"}> ```` So we have a unique instance of this flow and the instance has been saved in the database. The first step to be executed for this instance is "send_order_to_warehouse", so let's create a worker for this step. ####Defining workers for each step#### A workflow is comprised of steps, and each step needs a worker. Each worker will execute whatever it was meant to do, and then either: 1. Pass the workflow instance to the next step on the process, or 2. Delay the step execution for a later date, or 3. End the entire workflow process for the instance. Since send_order_to_warehouse is the first step in this workflow, let's build the worker for it first: ```` class SendOrderToWarehouseWorker < Seam::Worker def process # the current workflow instance is available as "effort" order = Order.find effort.data['order_id'] warehouse_service.send order # by default, if this worker completes with no error the workflow instance will be sent to the next step end end ```` If you name your class as a camel-case version of the step, Seam will automatically bind up the worker to the step. To execute the worker, use: ```` SendOrderToWarehouse.execute_all ```` This method will look for all workflow instances that are currently ready for the step in question. ####Progressing through the workflow#### By default, steps are considered as being completed when the worker completes successfully. There might be times where you don't want to go quickly, like the next step in this example: ```` class WaitForOrderToBeShippedWorker < Seam::Worker def process effort.data["shipping_status"] = # some method that returns the shipping status unless effort.data["shipping_status"] == "shipped" try_again_in 4.hours end end end ```` "try_again_in" can be used to signal that the step has not been completed and should be retried later. "eject" can also be used to signify that the entire workflow should be stopped, like so: ```` class WaitForOrderToBeShippedWorker < Seam::Worker def process effort.data["shipping_status"] = # some method that returns the shipping status if effort.data["shipping_status"] == "cancelled" eject # no need to continue! end end end ```` ####History#### As workflow instances progress through each step, the history of every operation will be stored. A history of the "data" block before and after each step run is also stored. The history is available through: ```` effort.history ```` ####Waiting#### Seam comes with a default worker for waiting. It can be defined by calling "wait" on a flow, like this. ```` flow = Seam::Flow.new flow.send_order_to_warehouse flow.wait 2.days flow.check_if_the_order_has_been_fulfilled ```` ## Installation Add this line to your application's Gemfile: gem 'seam' And then execute: $ bundle Or install it yourself as: $ gem install seam ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request