Skip to content

Instantly share code, notes, and snippets.

@kgrz
Forked from radar/0_example.rb
Created July 5, 2012 08:46
Show Gist options
  • Select an option

  • Save kgrz/3052379 to your computer and use it in GitHub Desktop.

Select an option

Save kgrz/3052379 to your computer and use it in GitHub Desktop.
Order.class_eval do
def define_state_machine
redefine_states!
go_to_state :address
go_to_state :delivery
go_to_state :payment, :if => lambda { payment_required? }
go_to_state :confirm, :if => lambda { confirmation_required? }
go_to_state :complete
remove_transition :from => :delivery, :to => :confirm
end
end

This is an example implementation of an order class that can have custom states defined for its instances. This could allow a flexible workflow for Spree.

The code works by calling the redefine_states! method which clears all current transitions. The go_to_state method then defines transitions for each step of the process. The first call will transition from cart, and subsequent calls will transition from the state before it.

If a state has a condition on it, then a record of all previous states are kept until there is a state that has no condition. Once this happens, the code will define state transitions for all state paths for the states in between.

The remove method will remove a transition that has previously been either automatically or manually defined. In the case of this state machine, the delivery to confirm transition has been automatically defined, but we don't want it. So we get rid of it.

Please review the code and attempt to run the specs and point out any issues you see.

require 'state_machine'
module Checkout
def self.included(klass)
klass.class_eval do
attr_accessor :state
attr_accessor :initial_state
attr_accessor :transitions
attr_accessor :previous_states
def initialize
super
define_state_machine
self.state = self.initial_state
end
def transitions
@transitions ||= []
end
def add_transition(options)
self.transitions << { options.delete(:from) => options.delete(:to) }.merge(options)
end
# TODO: Delegate
def next!
machine.next!
end
def machine
order = self
@machine ||= StateMachine.new(order, :initial => self.initial_state) do
order.transitions.each { |attrs| transition(attrs) }
# Persist the state on the order
after_transition do
order.state = order.machine.state
order.save
end
end
end
def redefine_states!
self.transitions.clear
self.previous_states = []
self.initial_state = :cart
self.previous_states << :cart
end
def go_to_state(name, options={})
if options[:if]
previous_states.each do |state|
add_transition({:from => state, :to => name, :on => :next}.merge(options))
end
self.previous_states << name
else
previous_states.each do |state|
add_transition({:from => state, :to => name, :on => :next}.merge(options))
end
self.previous_states = [name]
end
end
def remove_transition(options={})
if transition = find_transition(options)
self.transitions.delete(transition)
end
end
def find_transition(options={})
self.transitions.detect do |transition|
transition[options[:from].to_sym] == options[:to].to_sym
end
end
end
end
class StateMachine
def self.new(object, *args, &block)
machine = Class.new do
def definition
self.class.state_machine
end
end
machine.state_machine(*args, &block)
machine.new
end
end
end
class Order
include Checkout
def payment_required?
false
end
def confirmation_required?
false
end
def save
#noop
end
end
describe Order do
let(:order) { Order.new }
context "with default state machine" do
before do
Order.class_eval do
def define_state_machine
redefine_states!
go_to_state :address
go_to_state :delivery
go_to_state :payment, :if => lambda { payment_required? }
go_to_state :confirm, :if => lambda { confirmation_required? }
go_to_state :complete
remove_transition :from => :delivery, :to => :confirm
end
end
end
it "has the following transitions" do
transitions = [
{ :address => :delivery },
{ :delivery => :payment },
{ :payment => :confirm },
{ :confirm => :complete },
{ :payment => :complete },
{ :delivery => :complete }
]
transitions.each do |transition|
transition = order.find_transition(:from => transition.keys.first, :to => transition.values.first)
transition.should_not be_nil
end
end
it "does not have a transition from delivery to confirm" do
transition = order.find_transition(:from => :delivery, :to => :confirm)
transition.should be_nil
end
it "starts out at cart" do
order.state.should == :cart
end
it "transitions to address" do
order.next!
order.state.should == "address"
end
context "from address" do
before do
order.machine.state = 'address'
end
it "transitions to delivery" do
order.next!
order.state.should == "delivery"
end
end
context "from delivery" do
before do
order.machine.state = 'delivery'
end
context "with payment required" do
before do
order.stub :payment_required? => true
end
it "transitions to payment" do
order.next!
order.state.should == 'payment'
end
end
context "without payment required" do
before do
order.stub :payment_required? => false
end
it "transitions to complete" do
order.next!
order.state.should == "complete"
end
end
end
context "from payment" do
before do
order.machine.state = 'payment'
end
context "with confirmation required" do
before do
order.stub :confirmation_required? => true
end
it "transitions to confirm" do
order.next!
order.state.should == "confirm"
end
end
context "without confirmation required" do
before do
order.stub :confirmation_required? => false
end
it "transitions to complete" do
order.next!
order.state.should == "complete"
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment