README.md in seam-0.0.9 vs README.md in seam-0.0.10

- old
+ new

@@ -1,9 +1,139 @@ # Seam -TODO: Write a gem description +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: + +```` +<Seam::Effort + @completed_steps=[], + @history=[], + @complete=false, + @id="1ecc4cbe-16af-45f6-8532-7f37493ec11c", + @created_at=2013-08-20 22:58:07 -0500, + @next_execute_at=2013-08-20 22:58:07 -0500, + @next_step="send_order_to_warehouse", + @flow={"steps"=>[{"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 +```` + ## Installation Add this line to your application's Gemfile: gem 'seam' @@ -13,13 +143,9 @@ $ bundle Or install it yourself as: $ gem install seam - -## Usage - -TODO: Write usage instructions here ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`)