Skip to content
This repository

Dynamic state machine proposal #1743

Closed
wants to merge 12 commits into from

4 participants

Ryan Bigg Trung Lê Sean Schofield Gopal Rathore
Ryan Bigg
Collaborator
radar commented July 05, 2012

This issue should address #1418.

See the code in this gist to see what it looks like. As I explain there:

The code works by calling the checkout_flow 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. For instance, the follow transitions will be defined between delivery and complete:

  • Delivery to payment
  • Delivery to confirm
  • Delivery to complete
  • Payment to confirm
  • Payment to complete
  • Confirm to complete

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. As in the above example, the "delivery to confirm" transition will be removed.

What this will allow for is for dynamic state machine definitions for Order objects by re-defining the checkout_flow block on them and using the new state definition API.

This issue is just opening that implementation up for consideration. Does it look sensible? Does it fit the use cases? That kind of thing.

Thoughts?

Ryan Bigg radar closed this pull request from a commit July 09, 2012
Commit has since been removed from the repository and is no longer available.
Ryan Bigg radar closed this in fb59527 July 09, 2012
Ryan Bigg radar referenced this pull request from a commit July 09, 2012
Commit has since been removed from the repository and is no longer available.
Ryan Bigg radar reopened this July 09, 2012
Ryan Bigg radar commented on the diff July 09, 2012
core/app/models/spree/order.rb
... ...
@@ -450,6 +413,14 @@ def payment_method
450 413
       end
451 414
     end
452 415
 
  416
+    def process_payments!
1
Ryan Bigg Collaborator
radar added a note July 09, 2012

This just moves the begin/rescue logic out of the state machine and into this method, which I think is neater.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
core/app/models/spree/order.rb
... ...
@@ -576,13 +547,13 @@ def require_email
576 547
       end
577 548
 
578 549
       def has_available_shipment
579  
-        return unless :address == state_name.to_sym
  550
+        return unless address?
1
Ryan Bigg Collaborator
radar added a note July 09, 2012

mmm, query methods

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Ryan Bigg radar commented on the diff July 09, 2012
core/app/models/spree/order/checkout.rb
((3 lines not shown))
  3
+    module Checkout
  4
+      def self.included(klass)
  5
+        klass.class_eval do
  6
+          cattr_accessor :next_event_transitions
  7
+          cattr_accessor :previous_states
  8
+          cattr_accessor :checkout_flow
  9
+
  10
+          def self.checkout_flow(&block)
  11
+            if block_given?
  12
+              @checkout_flow = block
  13
+            else
  14
+              @checkout_flow
  15
+            end
  16
+          end
  17
+
  18
+          def self.define_state_machine!
1
Ryan Bigg Collaborator
radar added a note July 09, 2012

This method is the magic. This is called when Core::Engine does a to_prepare. This means that you can re-define the next state transitions as many times as you wish and every time to_prepare is called, the state machine will re-define the transitions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
core/app/models/spree/order/checkout.rb
((13 lines not shown))
  13
+            else
  14
+              @checkout_flow
  15
+            end
  16
+          end
  17
+
  18
+          def self.define_state_machine!
  19
+            @machine ||= begin
  20
+              self.previous_states = [:cart]
  21
+              instance_eval(&checkout_flow)
  22
+              klass = self
  23
+
  24
+              state_machine :state, :initial => :cart do
  25
+                klass.next_event_transitions.each { |t| transition(t.merge(:on => :next)) }
  26
+
  27
+                # Persist the state on the order
  28
+                after_transition do |order|
1
Ryan Bigg Collaborator
radar added a note July 09, 2012

I am not sure if this is actually required any more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
core/app/models/spree/order/checkout.rb
((55 lines not shown))
  55
+                end
  56
+
  57
+                before_transition :to => :delivery, :do => :remove_invalid_shipments!
  58
+
  59
+                after_transition :to => :complete, :do => :finalize!
  60
+                after_transition :to => :delivery, :do => :create_tax_charge!
  61
+                after_transition :to => :payment,  :do => :create_shipment!
  62
+                after_transition :to => :resumed,  :do => :after_resume
  63
+                after_transition :to => :canceled, :do => :after_cancel
  64
+              end
  65
+            end
  66
+          end
  67
+
  68
+          def self.go_to_state(name, options={})
  69
+            if options[:if]
  70
+              previous_states.each do |state|
1
Ryan Bigg Collaborator
radar added a note July 09, 2012

I thought about moving this out into its own method but decided against it because I am lazy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Ryan Bigg radar commented on the diff July 09, 2012
core/app/models/spree/order/checkout.rb
((67 lines not shown))
  67
+
  68
+          def self.go_to_state(name, options={})
  69
+            if options[:if]
  70
+              previous_states.each do |state|
  71
+                add_transition({:from => state, :to => name}.merge(options))
  72
+              end
  73
+              self.previous_states << name
  74
+            else
  75
+              previous_states.each do |state|
  76
+                add_transition({:from => state, :to => name}.merge(options))
  77
+              end
  78
+              self.previous_states = [name]
  79
+            end
  80
+          end
  81
+
  82
+          def self.remove_transition(options={})
1
Ryan Bigg Collaborator
radar added a note July 09, 2012

This was previously called remove, but I've renamed it to remove_transition so that there isn't a "mysterious" remove method on the Order class, and so that it fits more into the same theme as find_transition and add_transition.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Ryan Bigg radar commented on the diff July 09, 2012
core/app/models/spree/order/checkout.rb
((79 lines not shown))
  79
+            end
  80
+          end
  81
+
  82
+          def self.remove_transition(options={})
  83
+            if transition = find_transition(options)
  84
+              self.next_event_transitions.delete(transition)
  85
+            end
  86
+          end
  87
+
  88
+          def self.find_transition(options={})
  89
+            self.next_event_transitions.detect do |transition|
  90
+              transition[options[:from].to_sym] == options[:to].to_sym
  91
+            end
  92
+          end
  93
+
  94
+          def self.next_event_transitions
1
Ryan Bigg Collaborator
radar added a note July 09, 2012

This used to be called transitions. Same reasons for renaming the remove method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Ryan Bigg
Collaborator
radar commented July 10, 2012

Just a note: this doesn't actually change the checkout breadcrumbs yet. I've not yet begun work on that, but will probably knock it off tomorrow.

added some commits July 10, 2012
Ryan Bigg Implemented checkout_steps method
This replaces the code duplication in checkout_helper to determine what steps to show for an order during the checkout process
256d28f
Ryan Bigg checkout_steps should always return a string b5b4e54
Ryan Bigg
Collaborator
radar commented July 10, 2012

Checkout breadcrumbs are in @256d28f. The build is passing on Travis.

Ryan Bigg
Collaborator
radar commented July 12, 2012

Kicking the tyres some more on this today.

Ryan Bigg
Collaborator
radar commented July 25, 2012

I can't think of anything more to do on this. Can @BDQ, @cmar, @LBRapid, @joneslee85 and/or @schof look this over and see if they can break it?

Trung Lê
Collaborator

@radar Will spend sometime on this this weekend, see if I could break it

Sean Schofield
Owner

I'm ok to merge this as long as we update the documentation and release notes adequately (plus resolve conflicts of course.)

Ryan Bigg radar referenced this pull request from a commit July 10, 2012
Ryan Bigg Introduced customizable checkout flow
Merges #1743
fa1d66c
Ryan Bigg
Collaborator

Merged to master with @fa1d66c. Tests are running on master now. When they're good, I'll update the Checkout Customization guide appropriately.

Ryan Bigg radar closed this August 07, 2012
Gopal Rathore

I want to remove the Shipping page validations.. I used this code but still there is validations exist. plz help.

checkout_flow do
go_to_state :address, :if => lambda { |order| order.payment_required? }
go_to_state :payment, :if => lambda { |order| order.payment_required? }
go_to_state :confirm, :if => lambda { |order| order.confirmation_required? }
go_to_state :complete
remove_transition :from => :delivery, :to => :confirm
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
3  .travis.yml
@@ -23,8 +23,9 @@ notifications:
23 23
       - "irc.freenode.org#spree"
24 24
 branches:
25 25
   only:
  26
+    - flexible-checkout-flow
26 27
     - 1-0-stable
27  
-    - auth-take-two
  28
+    - 1-1-stable
28 29
     - master
29 30
 rvm:
30 31
   - 1.8.7
10  core/app/controllers/spree/checkout_controller.rb
@@ -6,6 +6,7 @@ class CheckoutController < BaseController
6 6
     ssl_required
7 7
 
8 8
     before_filter :load_order
  9
+    before_filter :ensure_valid_state
9 10
     before_filter :associate_user
10 11
     rescue_from Spree::Core::GatewayError, :with => :rescue_from_spree_gateway_error
11 12
 
@@ -37,6 +38,15 @@ def update
37 38
     end
38 39
 
39 40
     private
  41
+      def ensure_valid_state
  42
+        if params[:state] && params[:state] != 'cart' &&
  43
+           !@order.checkout_steps.include?(params[:state])
  44
+          params[:state] == 'cart'
  45
+          @order.state = 'cart'
  46
+          redirect_to checkout_path
  47
+        end
  48
+      end
  49
+
40 50
       def load_order
41 51
         @order = current_order
42 52
         redirect_to cart_path and return unless @order and @order.checkout_allowed?
6  core/app/helpers/spree/checkout_helper.rb
... ...
@@ -1,11 +1,7 @@
1 1
 module Spree
2 2
   module CheckoutHelper
3 3
     def checkout_states
4  
-      if @order.payment and @order.payment.payment_method.payment_profiles_supported?
5  
-        %w(address delivery payment confirm complete)
6  
-      else
7  
-        %w(address delivery payment complete)
8  
-      end
  4
+      @order.checkout_steps
9 5
     end
10 6
 
11 7
     def checkout_progress
85  core/app/models/spree/order.rb
@@ -2,6 +2,16 @@
2 2
 
3 3
 module Spree
4 4
   class Order < ActiveRecord::Base
  5
+    include Checkout
  6
+    checkout_flow do
  7
+      go_to_state :address
  8
+      go_to_state :delivery
  9
+      go_to_state :payment, :if => lambda { |order| order.payment_required? }
  10
+      go_to_state :confirm, :if => lambda { |order| order.confirmation_required? }
  11
+      go_to_state :complete
  12
+      remove_transition :from => :delivery, :to => :confirm
  13
+    end
  14
+
5 15
     token_resource
6 16
 
7 17
     attr_accessible :line_items, :bill_address_attributes, :ship_address_attributes, :payments_attributes,
@@ -54,54 +64,6 @@ class Order < ActiveRecord::Base
54 64
     class_attribute :update_hooks
55 65
     self.update_hooks = Set.new
56 66
 
57  
-    # order state machine (see http://github.com/pluginaweek/state_machine/tree/master for details)
58  
-    state_machine :initial => 'cart', :use_transactions => false do
59  
-
60  
-      event :next do
61  
-        transition :from => 'cart',     :to => 'address'
62  
-        transition :from => 'address',  :to => 'delivery'
63  
-        transition :from => 'delivery', :to => 'payment', :if => :payment_required?
64  
-        transition :from => 'delivery', :to => 'complete'
65  
-        transition :from => 'confirm',  :to => 'complete'
66  
-
67  
-        # note: some payment methods will not support a confirm step
68  
-        transition :from => 'payment',  :to => 'confirm',
69  
-                                        :if => Proc.new { |order| order.payment_method && order.payment_method.payment_profiles_supported? }
70  
-
71  
-        transition :from => 'payment', :to => 'complete'
72  
-      end
73  
-
74  
-      event :cancel do
75  
-        transition :to => 'canceled', :if => :allow_cancel?
76  
-      end
77  
-      event :return do
78  
-        transition :to => 'returned', :from => 'awaiting_return'
79  
-      end
80  
-      event :resume do
81  
-        transition :to => 'resumed', :from => 'canceled', :if => :allow_resume?
82  
-      end
83  
-      event :authorize_return do
84  
-        transition :to => 'awaiting_return'
85  
-      end
86  
-
87  
-      before_transition :to => 'complete' do |order|
88  
-        begin
89  
-          order.process_payments!
90  
-        rescue Core::GatewayError
91  
-          !!Spree::Config[:allow_checkout_on_gateway_error]
92  
-        end
93  
-      end
94  
-
95  
-      before_transition :to => 'delivery', :do => :remove_invalid_shipments!
96  
-
97  
-      after_transition :to => 'complete', :do => :finalize!
98  
-      after_transition :to => 'delivery', :do => :create_tax_charge!
99  
-      after_transition :to => 'payment',  :do => :create_shipment!
100  
-      after_transition :to => 'resumed',  :do => :after_resume
101  
-      after_transition :to => 'canceled', :do => :after_cancel
102  
-
103  
-    end
104  
-
105 67
     def self.by_number(number)
106 68
       where(:number => number)
107 69
     end
@@ -156,6 +118,11 @@ def payment_required?
156 118
       total.to_f > 0.0
157 119
     end
158 120
 
  121
+    # If true, causes the confirmation step to happen during the checkout process
  122
+    def confirmation_required?
  123
+      payment_method && payment_method.payment_profiles_supported?
  124
+    end
  125
+
159 126
     # Indicates the number of items in the order
160 127
     def item_count
161 128
       line_items.sum(:quantity)
@@ -361,7 +328,6 @@ def create_shipment!
361 328
                                           :address => self.ship_address,
362 329
                                           :inventory_units => self.inventory_units}, :without_protection => true)
363 330
       end
364  
-
365 331
     end
366 332
 
367 333
     def outstanding_balance
@@ -383,10 +349,6 @@ def credit_cards
383 349
       CreditCard.scoped(:conditions => { :id => credit_card_ids })
384 350
     end
385 351
 
386  
-    def process_payments!
387  
-      ret = payments.each(&:process!)
388  
-    end
389  
-
390 352
     # Finalizes an in progress order after checkout is complete.
391 353
     # Called after transition to complete state when payments will have been processed
392 354
     def finalize!
@@ -450,6 +412,14 @@ def payment_method
450 412
       end
451 413
     end
452 414
 
  415
+    def process_payments!
  416
+      begin
  417
+        payments.each(&:process!)
  418
+      rescue Core::GatewayError
  419
+        !!Spree::Config[:allow_checkout_on_gateway_error]
  420
+      end
  421
+    end
  422
+
453 423
     def billing_firstname
454 424
       bill_address.try(:firstname)
455 425
     end
@@ -473,6 +443,10 @@ def merge!(order)
473 443
       order.destroy
474 444
     end
475 445
 
  446
+    def has_step?(step)
  447
+      checkout_steps.include?(step)
  448
+    end
  449
+
476 450
     private
477 451
       def link_by_email
478 452
         self.email = user.email if self.user and not user.anonymous?
@@ -576,13 +550,14 @@ def require_email
576 550
       end
577 551
 
578 552
       def has_available_shipment
579  
-        return unless :address == state_name.to_sym
  553
+        return unless has_step?("delivery")
  554
+        return unless address?
580 555
         return unless ship_address && ship_address.valid?
581 556
         errors.add(:base, :no_shipping_methods_available) if available_shipping_methods.empty?
582 557
       end
583 558
 
584 559
       def has_available_payment
585  
-        return unless :delivery == state_name.to_sym
  560
+        return unless delivery?
586 561
         errors.add(:base, :no_payment_methods_available) if available_payment_methods.empty?
587 562
       end
588 563
 
124  core/app/models/spree/order/checkout.rb
... ...
@@ -0,0 +1,124 @@
  1
+module Spree
  2
+  class Order < ActiveRecord::Base
  3
+    module Checkout
  4
+      def self.included(klass)
  5
+        klass.class_eval do
  6
+          cattr_accessor :next_event_transitions
  7
+          cattr_accessor :previous_states
  8
+          cattr_accessor :checkout_flow
  9
+          cattr_accessor :checkout_steps
  10
+
  11
+          def self.checkout_flow(&block)
  12
+            if block_given?
  13
+              @checkout_flow = block
  14
+            else
  15
+              @checkout_flow
  16
+            end
  17
+          end
  18
+
  19
+          def self.define_state_machine!
  20
+            self.checkout_steps = []
  21
+            self.next_event_transitions = []
  22
+            self.previous_states = [:cart]
  23
+            instance_eval(&checkout_flow)
  24
+            klass = self
  25
+
  26
+            state_machine :state, :initial => :cart do
  27
+              klass.next_event_transitions.each { |t| transition(t.merge(:on => :next)) }
  28
+
  29
+              # Persist the state on the order
  30
+              after_transition do |order|
  31
+                order.state = order.state
  32
+                order.save
  33
+              end
  34
+
  35
+              event :cancel do
  36
+                transition :to => :canceled, :if => :allow_cancel?
  37
+              end
  38
+
  39
+              event :return do
  40
+                transition :to => :returned, :from => :awaiting_return
  41
+              end
  42
+
  43
+              event :resume do
  44
+                transition :to => :resumed, :from => :canceled, :if => :allow_resume?
  45
+              end
  46
+
  47
+              event :authorize_return do
  48
+                transition :to => :awaiting_return
  49
+              end
  50
+
  51
+              before_transition :to => :complete do |order|
  52
+                begin
  53
+                  order.process_payments!
  54
+                rescue Spree::Core::GatewayError
  55
+                  !!Spree::Config[:allow_checkout_on_gateway_error]
  56
+                end
  57
+              end
  58
+
  59
+              before_transition :to => :delivery, :do => :remove_invalid_shipments!
  60
+
  61
+              after_transition :to => :complete, :do => :finalize!
  62
+              after_transition :to => :delivery, :do => :create_tax_charge!
  63
+              after_transition :to => :resumed,  :do => :after_resume
  64
+              after_transition :to => :canceled, :do => :after_cancel
  65
+
  66
+              after_transition :from => :delivery,  :do => :create_shipment!
  67
+            end
  68
+          end
  69
+
  70
+          def self.go_to_state(name, options={})
  71
+            self.checkout_steps[name] = options
  72
+            if options[:if]
  73
+              previous_states.each do |state|
  74
+                add_transition({:from => state, :to => name}.merge(options))
  75
+              end
  76
+              self.previous_states << name
  77
+            else
  78
+              previous_states.each do |state|
  79
+                add_transition({:from => state, :to => name}.merge(options))
  80
+              end
  81
+              self.previous_states = [name]
  82
+            end
  83
+          end
  84
+
  85
+          def self.remove_transition(options={})
  86
+            if transition = find_transition(options)
  87
+              self.next_event_transitions.delete(transition)
  88
+            end
  89
+          end
  90
+
  91
+          def self.find_transition(options={})
  92
+            self.next_event_transitions.detect do |transition|
  93
+              transition[options[:from].to_sym] == options[:to].to_sym
  94
+            end
  95
+          end
  96
+
  97
+          def self.next_event_transitions
  98
+            @next_event_transitions ||= []
  99
+          end
  100
+
  101
+          def self.checkout_steps
  102
+            @checkout_steps ||= ActiveSupport::OrderedHash.new
  103
+          end
  104
+
  105
+          def self.add_transition(options)
  106
+            self.next_event_transitions << { options.delete(:from) => options.delete(:to) }.merge(options)
  107
+          end
  108
+
  109
+          def checkout_steps
  110
+            checkout_steps = []
  111
+            # TODO: replace this with each_with_object once Ruby 1.9 is standard
  112
+            self.class.checkout_steps.each do |step, options|
  113
+              if options[:if]
  114
+                next unless options[:if].call(self)
  115
+              end
  116
+              checkout_steps << step
  117
+            end
  118
+            checkout_steps.map(&:to_s)
  119
+          end
  120
+        end
  121
+      end
  122
+    end
  123
+  end
  124
+end
14  core/app/views/spree/shared/_order_details.html.erb
@@ -14,12 +14,14 @@
14 14
     </div>
15 15
   </div>
16 16
 
17  
-  <div class="columns alpha four">
18  
-    <h6><%= t(:shipping_method) %> <%= link_to "(#{t(:edit)})", checkout_state_path(:delivery) unless @order.completed? %></h6>
19  
-    <div class="delivery">
20  
-      <%= order.shipping_method.name %>
  17
+  <% if @order.has_step?("delivery") %>
  18
+    <div class="columns alpha four">
  19
+      <h6><%= t(:shipping_method) %> <%= link_to "(#{t(:edit)})", checkout_state_path(:delivery) unless @order.completed? %></h6>
  20
+      <div class="delivery">
  21
+        <%= order.shipping_method.name %>
  22
+      </div>
21 23
     </div>
22  
-  </div>
  24
+  <% end %>
23 25
 
24 26
   <div class="columns omega four">
25 27
     <h6><%= t(:payment_information) %> <%= link_to "(#{t(:edit)})", checkout_state_path(:payment) unless @order.completed? %></h6>
@@ -110,4 +112,4 @@
110 112
       </tr>
111 113
     <% end %>
112 114
   </tfoot>
113  
-</table>
  115
+</table>
4  core/lib/spree/core/engine.rb
@@ -10,11 +10,14 @@ class Engine < ::Rails::Engine
10 10
       config.autoload_paths += %W(#{config.root}/lib)
11 11
 
12 12
       def self.activate
  13
+        Spree::Order.define_state_machine!
13 14
       end
14 15
 
15 16
       config.to_prepare &method(:activate).to_proc
16 17
 
17 18
       config.after_initialize do
  19
+        Spree::Order.define_state_machine!
  20
+
18 21
         ActiveSupport::Notifications.subscribe(/^spree\./) do |*args|
19 22
           event_name, start_time, end_time, id, payload = args
20 23
           Activator.active.event_name_starts_with(event_name).each do |activator|
@@ -22,7 +25,6 @@ def self.activate
22 25
             activator.activate(payload)
23 26
           end
24 27
         end
25  
-
26 28
       end
27 29
 
28 30
       # We need to reload the routes here due to how Spree sets them up.
141  core/spec/models/order/checkout_spec.rb
... ...
@@ -0,0 +1,141 @@
  1
+require 'spec_helper'
  2
+
  3
+describe Spree::Order do
  4
+  let(:order) { Spree::Order.new }
  5
+  context "with default state machine" do
  6
+    it "has the following transitions" do
  7
+      transitions = [
  8
+        { :address => :delivery },
  9
+        { :delivery => :payment },
  10
+        { :payment => :confirm },
  11
+        { :confirm => :complete },
  12
+        { :payment => :complete },
  13
+        { :delivery => :complete }
  14
+      ]
  15
+      transitions.each do |transition|
  16
+        transition = Spree::Order.find_transition(:from => transition.keys.first, :to => transition.values.first)
  17
+        transition.should_not be_nil
  18
+      end
  19
+    end
  20
+
  21
+    it "does not have a transition from delivery to confirm" do
  22
+      transition = Spree::Order.find_transition(:from => :delivery, :to => :confirm)
  23
+      transition.should be_nil
  24
+    end
  25
+
  26
+    context "#checkout_steps" do
  27
+      context "when confirmation not required" do
  28
+        before do
  29
+          order.stub :confirmation_required? => false
  30
+          order.stub :payment_required? => true
  31
+        end
  32
+
  33
+        specify do
  34
+          order.checkout_steps.should == %w(address delivery payment complete)
  35
+        end
  36
+      end
  37
+
  38
+      context "when confirmation required" do
  39
+        before do
  40
+          order.stub :confirmation_required? => true
  41
+          order.stub :payment_required? => true
  42
+        end
  43
+
  44
+        specify do
  45
+          order.checkout_steps.should == %w(address delivery payment confirm complete)
  46
+        end
  47
+      end
  48
+
  49
+      context "when payment not required" do
  50
+        before { order.stub :payment_required? => false }
  51
+        specify do
  52
+          order.checkout_steps.should == %w(address delivery complete)
  53
+        end
  54
+      end
  55
+
  56
+      context "when payment required" do
  57
+        before { order.stub :payment_required? => true }
  58
+        specify do
  59
+          order.checkout_steps.should == %w(address delivery payment complete)
  60
+        end
  61
+      end
  62
+    end
  63
+
  64
+    it "starts out at cart" do
  65
+      order.state.should == "cart"
  66
+    end
  67
+
  68
+    it "transitions to address" do
  69
+      order.next!
  70
+      order.state.should == "address"
  71
+    end
  72
+
  73
+    context "from address" do
  74
+      before do
  75
+        order.state = 'address'
  76
+      end
  77
+
  78
+      it "transitions to delivery" do
  79
+        order.stub(:has_available_payment)
  80
+        order.next!
  81
+        order.state.should == "delivery"
  82
+      end
  83
+    end
  84
+
  85
+    context "from delivery" do
  86
+      before do
  87
+        order.state = 'delivery'
  88
+      end
  89
+
  90
+      context "with payment required" do
  91
+        before do
  92
+          order.stub :payment_required? => true
  93
+        end
  94
+
  95
+        it "transitions to payment" do
  96
+          order.next!
  97
+          order.state.should == 'payment'
  98
+        end
  99
+      end
  100
+
  101
+      context "without payment required" do
  102
+        before do
  103
+          order.stub :payment_required? => false
  104
+        end
  105
+
  106
+        it "transitions to complete" do
  107
+          order.next!
  108
+          order.state.should == "complete"
  109
+        end
  110
+      end
  111
+    end
  112
+
  113
+    context "from payment" do
  114
+      before do
  115
+        order.state = 'payment'
  116
+      end
  117
+
  118
+      context "with confirmation required" do
  119
+        before do
  120
+          order.stub :confirmation_required? => true
  121
+        end
  122
+
  123
+        it "transitions to confirm" do
  124
+          order.next!
  125
+          order.state.should == "confirm"
  126
+        end
  127
+      end
  128
+
  129
+      context "without confirmation required" do
  130
+        before do
  131
+          order.stub :confirmation_required? => false
  132
+        end
  133
+
  134
+        it "transitions to complete" do
  135
+          order.next!
  136
+          order.state.should == "complete"
  137
+        end
  138
+      end
  139
+    end
  140
+  end
  141
+end
12  core/spec/models/order_spec.rb
@@ -134,6 +134,7 @@ def compute(computable)
134 134
   end
135 135
 
136 136
   context "#next!" do
  137
+    before { order.stub(:require_email => false) }
137 138
     context "when current state is confirm" do
138 139
       before { order.state = "confirm" }
139 140
       it "should finalize order when transitioning to complete state" do
@@ -164,13 +165,10 @@ def compute(computable)
164 165
            end
165 166
 
166 167
            it "should complete the order" do
167  
-             pending
168  
-              order.next
169  
-              order.state.should == "complete"
170  
-            end
171  
-
  168
+             order.next
  169
+             order.state.should == "complete"
  170
+           end
172 171
          end
173  
-
174 172
        end
175 173
     end
176 174
     context "when current state is address" do
@@ -1006,7 +1004,7 @@ def compute(computable)
1006 1004
 
1007 1005
   context "#add_variant" do
1008 1006
     it "should update order totals" do
1009  
-      order = Spree::Order.create!
  1007
+      order = Spree::Order.create
1010 1008
 
1011 1009
       order.item_total.to_f.should == 0.00
1012 1010
       order.total.to_f.should == 0.00
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.