Dynamic state machine proposal #1743

Closed
wants to merge 12 commits into
from

Projects

None yet

4 participants

@radar
Member
radar commented Jul 5, 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?

@radar radar closed this in fb59527 Jul 10, 2012
@radar radar reopened this Jul 10, 2012
@radar radar commented on the diff Jul 10, 2012
core/app/models/spree/order.rb
@@ -450,6 +413,14 @@ def payment_method
end
end
+ def process_payments!
@radar
radar Jul 10, 2012 Spree Commerce member

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

@radar radar commented on an outdated diff Jul 10, 2012
core/app/models/spree/order.rb
@@ -576,13 +547,13 @@ def require_email
end
def has_available_shipment
- return unless :address == state_name.to_sym
+ return unless address?
@radar
radar Jul 10, 2012 Spree Commerce member

mmm, query methods

@radar radar commented on the diff Jul 10, 2012
core/app/models/spree/order/checkout.rb
+ module Checkout
+ def self.included(klass)
+ klass.class_eval do
+ cattr_accessor :next_event_transitions
+ cattr_accessor :previous_states
+ cattr_accessor :checkout_flow
+
+ def self.checkout_flow(&block)
+ if block_given?
+ @checkout_flow = block
+ else
+ @checkout_flow
+ end
+ end
+
+ def self.define_state_machine!
@radar
radar Jul 10, 2012 Spree Commerce member

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.

@radar radar commented on an outdated diff Jul 10, 2012
core/app/models/spree/order/checkout.rb
+ else
+ @checkout_flow
+ end
+ end
+
+ def self.define_state_machine!
+ @machine ||= begin
+ self.previous_states = [:cart]
+ instance_eval(&checkout_flow)
+ klass = self
+
+ state_machine :state, :initial => :cart do
+ klass.next_event_transitions.each { |t| transition(t.merge(:on => :next)) }
+
+ # Persist the state on the order
+ after_transition do |order|
@radar
radar Jul 10, 2012 Spree Commerce member

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

@radar radar commented on an outdated diff Jul 10, 2012
core/app/models/spree/order/checkout.rb
+ end
+
+ before_transition :to => :delivery, :do => :remove_invalid_shipments!
+
+ after_transition :to => :complete, :do => :finalize!
+ after_transition :to => :delivery, :do => :create_tax_charge!
+ after_transition :to => :payment, :do => :create_shipment!
+ after_transition :to => :resumed, :do => :after_resume
+ after_transition :to => :canceled, :do => :after_cancel
+ end
+ end
+ end
+
+ def self.go_to_state(name, options={})
+ if options[:if]
+ previous_states.each do |state|
@radar
radar Jul 10, 2012 Spree Commerce member

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

@radar radar commented on the diff Jul 10, 2012
core/app/models/spree/order/checkout.rb
+
+ def self.go_to_state(name, options={})
+ if options[:if]
+ previous_states.each do |state|
+ add_transition({:from => state, :to => name}.merge(options))
+ end
+ self.previous_states << name
+ else
+ previous_states.each do |state|
+ add_transition({:from => state, :to => name}.merge(options))
+ end
+ self.previous_states = [name]
+ end
+ end
+
+ def self.remove_transition(options={})
@radar
radar Jul 10, 2012 Spree Commerce member

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.

@radar radar commented on the diff Jul 10, 2012
core/app/models/spree/order/checkout.rb
+ end
+ end
+
+ def self.remove_transition(options={})
+ if transition = find_transition(options)
+ self.next_event_transitions.delete(transition)
+ end
+ end
+
+ def self.find_transition(options={})
+ self.next_event_transitions.detect do |transition|
+ transition[options[:from].to_sym] == options[:to].to_sym
+ end
+ end
+
+ def self.next_event_transitions
@radar
radar Jul 10, 2012 Spree Commerce member

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

@radar
Member
radar commented Jul 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.

radar added some commits Jul 10, 2012
@radar radar 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
@radar radar checkout_steps should always return a string b5b4e54
@radar
Member
radar commented Jul 10, 2012

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

@radar
Member
radar commented Jul 13, 2012

Kicking the tyres some more on this today.

@radar
Member
radar commented Jul 26, 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?

@joneslee85
Member

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

@schof
Member
schof commented Aug 6, 2012

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

@radar radar added a commit that referenced this pull request Aug 8, 2012
@radar radar Introduced customizable checkout flow
Merges #1743
fa1d66c
@radar
Member
radar commented Aug 8, 2012

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

@radar radar closed this Aug 8, 2012
@gsnarawat

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