spree/spree — pull request #2969 by robertoles (2013-05-02 13:39:48+00:00)
Title: Adding methods for inserting and removing checkout steps for an order

Hi, I wanted to make defining checkout flow in extensions less likely to conflict with each other, so that we only have to specify the step we would like to include rather than redefine the whole checkout flow. Best to explain with an example.

If we have create a new spree store, and we want to add two fictional plugins 'product_engraving', to allow customers to add a message which will be engraved in to a product, and 'free_gifts' which allow a customer to add a free gift at the checkout stage if they are eligible.

product_engraving might define an order decorator like this...

```ruby
Spree::Order.class_eval do
checkout_flow do
go_to_state :address
go_to_state :delivery
go_to_state :engraving # step added
go_to_state :payment
go_to_state :confirm
go_to_state :complete
end
end
```

free_gifts might define an order decorator like this...

```ruby
Spree::Order.class_eval do
checkout_flow do
go_to_state :select_gift, if: lambda { eligible_for_gift? } # step added
go_to_state :address
go_to_state :delivery
go_to_state :payment
go_to_state :confirm
go_to_state :complete
end
end
```

In our Gemfile for the project we add in both extensions:

```ruby
...
gem 'product_engraving'
gem 'free_gifts'
...
```

The issue that arises is that the checkout step for the product engraving is removed when the state machine is reprocessed for the second the step for product engraving becomes orphaned from the checkout flow. This forces the user to implement their own Order decorator and define a custom checkout_flow to fix the problem.
I know this isnt a massive problem, but for people unfamiliar with the internals of the checkout system, figuring out the 'bug' might be quite taxing and time consuming. To solve this I have written two methods (insert_checkout_step, remove_checkout_step) to allow extension developers to insert or remove checkout steps without totally re-writing the checkout flow.

To insert a new step you would...

```ruby
Spree::Order.class_eval do
insert_checkout_step :engraving, after: :delivery
end
```

To insert a new step with an if option...
```ruby
Spree::Order.class_eval do
insert_checkout_step :select_gift, before: :address, if: { eligible_for_gift? }
end
```

To remove a step...
```ruby
Spree::Order.class_eval do
remove_checkout_step :delivery
end
```

I hope you dont mind me sending this PR to 1-3-stable, as thats what im currently using for customers.