active_cart 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,3 +1,3 @@
1
- active-cart.gemspec
1
+ *.gemspec
2
2
  pkg
3
3
  rdoc
data/Rakefile CHANGED
@@ -14,6 +14,7 @@ begin
14
14
  gem.homepage = "http://gemcutter.org/gems/active-cart"
15
15
  gem.authors = ["Myles Eftos"]
16
16
  gem.version = File.exist?('VERSION') ? File.read('VERSION') : ""
17
+ gem.add_dependency 'aasm'
17
18
  end
18
19
  Jeweler::GemcutterTasks.new
19
20
  rescue LoadError
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.2
@@ -9,10 +9,19 @@ module ActiveCart
9
9
  # The Cart object delegates a number of Array methods:
10
10
  # :[], :<<, :[]=, :at, :clear, :collect, :map, :delete, :delete_at, :each, :each_index, :empty?, :eql?, :first, :include?, :index, :inject, :last, :length, :pop, :push, :shift, :size, :unshift
11
11
  #
12
+ # The CartEngine object uses a state machine to track the state of the cart. The default states are: shopping, checkout, verifying_payment, completed, failed. It exposed the following transitions:
13
+ # continue_shopping, checkout, check_payment, payment_successful, payment_failed
14
+ #
15
+ # @cart.checkout! # transitions from shopping or verifying_payment to checkout
16
+ # @cart.check_payment! # transistions from checkout to verifying_payment
17
+ # @cart.payment_successful! # transitions from verifying_payment to completed
18
+ # @cart.payment_failed! # transitions from verifying_payment to failed
19
+ # @cart.continue_shopping # transitions from checkout or verifying_payment to shopping
20
+ #
12
21
  class Cart
13
22
  attr_accessor :storage_engine, :order_total_calculators, :customer
14
23
  include Enumerable
15
-
24
+
16
25
  # The method MUST be called before you call instance, otherwise you will receive and StandardError
17
26
  # You need to supply a storage engine. An optional block can be given which allows you to add order total items.
18
27
  #
@@ -71,7 +80,9 @@ module ActiveCart
71
80
  # @cart[1].size # => 4
72
81
  #
73
82
  def add_to_cart(item, quantity = 1)
83
+ return false unless item.before_add_to_cart(quantity) if item.respond_to?(:before_add_to_cart)
74
84
  @storage_engine.add_to_cart(item, quantity)
85
+ item.after_add_to_cart(quantity) if item.respond_to?(:after_add_to_cart)
75
86
  end
76
87
 
77
88
  # Removes an item from the cart (identified by the id of the item). If the supplied quantity is greater than equal to the number in the cart, the item will be removed, otherwise the quantity will simply be decremented by the supplied amount
@@ -84,7 +95,9 @@ module ActiveCart
84
95
  # @cart[0] # => nil
85
96
  #
86
97
  def remove_from_cart(item, quantity = 1)
98
+ return false unless item.before_remove_from_cart(quantity) if item.respond_to?(:before_remove_from_cart)
87
99
  @storage_engine.remove_from_cart(item, quantity)
100
+ item.after_remove_from_cart(quantity) if item.respond_to?(:after_remove_from_cart)
88
101
  end
89
102
 
90
103
  # Returns the total of the cart. This includes all the order_total calculations
@@ -92,5 +105,36 @@ module ActiveCart
92
105
  def total
93
106
  sub_total + order_total_calculators.total
94
107
  end
108
+
109
+ # Returns the current state of the cart storage engine
110
+ #
111
+ def state
112
+ storage_engine.state
113
+ end
114
+
115
+ # Transitions to the cart's :shopping state
116
+ def continue_shopping!
117
+ storage_engine.continue_shopping!
118
+ end
119
+
120
+ # Transitions to the cart's :checkout state
121
+ def checkout!
122
+ storage_engine.checkout!
123
+ end
124
+
125
+ # Transitions to the cart's :verifying_payment state
126
+ def check_payment!
127
+ storage_engine.check_payment!
128
+ end
129
+
130
+ # Transitions to the cart's :successful state
131
+ def payment_successful!
132
+ storage_engine.payment_successful!
133
+ end
134
+
135
+ # Transitions to the cart's :failed state
136
+ def payment_failed!
137
+ storage_engine.payment_failed!
138
+ end
95
139
  end
96
140
  end
@@ -1,7 +1,109 @@
1
- # Mixin this module into the class you want to user as your storage class. Remember to override the invoice_id method
1
+ # Mixin this module into the class you want to use as your storage class. Remember to override the invoice_id method
2
2
  #
3
3
  module ActiveCart
4
+ # The CartStorage object uses a state machine to track the state of the cart. The default states are: shopping, checkout, verifying_payment, completed, failed. It exposed the following transitions:
5
+ # continue_shopping, checkout, check_payment, payment_successful, payment_failed
6
+ #
7
+ # @cart.checkout! # transitions from shopping or verifying_payment to checkout
8
+ # @cart.check_payment! # transistions from checkout to verifying_payment
9
+ # @cart.payment_successful! # transitions from verifying_payment to completed
10
+ # @cart.payment_failed! # transitions from verifying_payment to failed
11
+ # @cart.continue_shopping # transitions from checkout or verifying_payment to shopping
12
+ #
13
+ # It will fire before_ and after callbacks with the same name as the transitions
14
+ #
4
15
  module CartStorage
16
+ def self.included(base) #:nodoc:
17
+ base.send :include, AASM
18
+ base.aasm_initial_state :shopping
19
+ base.aasm_state :shopping
20
+ base.aasm_state :checkout
21
+ base.aasm_state :verifying_payment
22
+ base.aasm_state :completed
23
+ base.aasm_state :failed
24
+
25
+ base.aasm_event :continue_shopping do
26
+ transitions :from => [ :checkout, :verifying_payment ], :to => :shopping, :enter => :before_continue_shopping, :exit => :after_continue_shopping
27
+ end
28
+
29
+ base.aasm_event :checkout do
30
+ transitions :from => [ :shopping, :verifying_payment ], :to => :checkout, :enter => :before_checkout, :exit => :after_checkout
31
+ end
32
+
33
+ base.aasm_event :check_payment do
34
+ transitions :from => :checkout, :to => :verifying_payment, :enter => :before_check_payment, :exit => :after_check_payment
35
+ end
36
+
37
+ base.aasm_event :payment_successful do
38
+ transitions :from => :verifying_payment, :to => :completed, :enter => :before_payment_successful, :exit => :after_payment_successful
39
+ end
40
+
41
+ base.aasm_event :payment_failed do
42
+ transitions :from => :verifying_payment, :to => :failed, :enter => :before_payment_failed, :exit => :after_payment_failed
43
+ end
44
+ end
45
+
46
+ # Called before transitioning into continuing_shopping
47
+ #
48
+ def before_continuing_shopping; end
49
+
50
+ # Called after transitioning into continuing_shopping
51
+ #
52
+ def after_continuing_shopping; end
53
+
54
+ # Called before transitioning into checkout
55
+ #
56
+ def before_checkout; end
57
+
58
+ # Called after transitioning into checkout
59
+ #
60
+ def after_checkout; end
61
+
62
+ # Called before transitioning into check_payment
63
+ #
64
+ def before_check_payment; end
65
+
66
+ # Called after transitioning into check_payment
67
+ #
68
+ def after_check_payment; end
69
+
70
+ # Called before transitioning into payment_successful
71
+ #
72
+ def before_payment_successful; end
73
+
74
+ # Called after transitioning into payment_successful
75
+ #
76
+ def after_payment_successful; end
77
+
78
+ # Called before transitioning into failed
79
+ #
80
+ def before_payment_failed; end
81
+
82
+ # Called after transitioning into failed
83
+ #
84
+ def after_payment_failed; end
85
+
86
+ # Returns the unique invoice_id for this cart instance. This MUST be overriden by the concrete class this module is mixed into, otherwise you
87
+ # will get a NotImplementedError
88
+ #
89
+ def invoice_id
90
+ raise NotImplementedError
91
+ end
92
+
93
+ # Returns the sub-total of all the items in the cart. Usually returns a float.
94
+ #
95
+ # @cart.sub_total # => 100.00
96
+ #
97
+ def sub_total
98
+ inject(0) { |t, item| t + (item.quantity * item.price) }
99
+ end
100
+
101
+ # Returns the number of items in the cart. It takes into account the individual quantities of each item, eg if there are 3 items in the cart, each with a quantity of 2, this will return 6
102
+ #
103
+ def quantity
104
+ inject(0) { |t, item| t + item.quantity }
105
+ end
106
+
5
107
  # Returns the unique invoice_id for this cart instance. This MUST be overriden by the concrete class this module is mixed into, otherwise you
6
108
  # will get a NotImplementedError
7
109
  #
@@ -67,5 +169,9 @@ module ActiveCart
67
169
  end
68
170
  end
69
171
  end
172
+
173
+ def state
174
+ return aasm_current_state
175
+ end
70
176
  end
71
177
  end
@@ -0,0 +1,36 @@
1
+ # Mixin this module into the class you want to act as an item
2
+ #
3
+ module ActiveCart
4
+ #
5
+ module Item
6
+ # A unique id for the item. The Mixee needs to implement this or a NotImplementedError will be thrown
7
+ #
8
+ def id
9
+ raise NotImplementedError
10
+ end
11
+
12
+ # A display name for the item. The Mixee needs to implement this or a NotImplementedError will be thrown
13
+ #
14
+ def name
15
+ raise NotImplementedError
16
+ end
17
+
18
+ # Returns the quantity of this item in the context of a cart
19
+ #
20
+ def quantity
21
+ @quantity || 0
22
+ end
23
+
24
+ # Set the quantity of this item in the context of a cart
25
+ #
26
+ def quantity=(quantity)
27
+ @quantity = quantity
28
+ end
29
+
30
+ # Returns the price of this item
31
+ #
32
+ def price
33
+ raise NotImplementedError
34
+ end
35
+ end
36
+ end
data/lib/active_cart.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  $:.unshift(File.join(File.dirname(__FILE__), 'active_cart'))
2
2
 
3
+ require 'rubygems'
3
4
  require 'singleton'
4
5
  require 'forwardable'
6
+ require 'aasm'
7
+ require 'active_cart/item'
5
8
  require 'active_cart/cart_storage'
6
9
  require 'active_cart/order_total'
7
10
  require 'active_cart/order_total_collection'
@@ -1,5 +1,7 @@
1
1
  class TestItem
2
- attr_accessor :id, :price, :quantity
2
+ include ActiveCart::Item
3
+
4
+ attr_accessor :id
3
5
 
4
6
  def ==(item)
5
7
  self.id == item.id
@@ -7,14 +9,25 @@ class TestItem
7
9
 
8
10
  def initialize(id = 1)
9
11
  self.id = id
10
- self.quantity = 0
12
+ end
13
+
14
+ def id
15
+ @id
16
+ end
17
+
18
+ def name
19
+ "Test item"
11
20
  end
12
21
 
13
22
  def price
14
23
  @price || 2
15
24
  end
16
25
 
26
+ def price=(price)
27
+ @price = price
28
+ end
29
+
17
30
  def inspect
18
- "TestItem: #{self.id}: #{self.quantity}x$#{self.price}"
31
+ "#{name}: #{self.id}: #{self.quantity}x$#{self.price}"
19
32
  end
20
33
  end
@@ -7,6 +7,65 @@ class CartStorageTest < Test::Unit::TestCase
7
7
  @cart = ActiveCart::Cart.new(@cart_storage_engine)
8
8
  end
9
9
 
10
+ context 'states' do
11
+ context 'checkout' do
12
+ should 'default to shopping' do
13
+ assert_equal :shopping, @cart_storage_engine.state
14
+ end
15
+
16
+ should 'transition from shopping to checkout' do
17
+ assert_nothing_raised do
18
+ @cart_storage_engine.checkout!
19
+ end
20
+ assert_equal :checkout, @cart_storage_engine.state
21
+ end
22
+
23
+ should 'transition from checkout to check_payment' do
24
+ @cart_storage_engine.checkout!
25
+ assert_nothing_raised do
26
+ @cart_storage_engine.check_payment!
27
+ end
28
+ assert_equal :verifying_payment, @cart_storage_engine.state
29
+ end
30
+
31
+ should 'transition from verifying_payment to completed' do
32
+ @cart_storage_engine.checkout!
33
+ @cart_storage_engine.check_payment!
34
+ assert_nothing_raised do
35
+ @cart_storage_engine.payment_successful!
36
+ end
37
+ assert_equal :completed, @cart_storage_engine.state
38
+ end
39
+
40
+ should 'transition from verifying_payment to shopping' do
41
+ @cart_storage_engine.checkout!
42
+ @cart_storage_engine.check_payment!
43
+ assert_nothing_raised do
44
+ @cart_storage_engine.continue_shopping!
45
+ end
46
+ assert_equal :shopping, @cart_storage_engine.state
47
+ end
48
+
49
+ should 'transition from verifying_payment to checkout' do
50
+ @cart_storage_engine.checkout!
51
+ @cart_storage_engine.check_payment!
52
+ assert_nothing_raised do
53
+ @cart_storage_engine.checkout!
54
+ end
55
+ assert_equal :checkout, @cart_storage_engine.state
56
+ end
57
+
58
+ should 'transition from verifying_payment to failed' do
59
+ @cart_storage_engine.checkout!
60
+ @cart_storage_engine.check_payment!
61
+ assert_nothing_raised do
62
+ @cart_storage_engine.payment_failed!
63
+ end
64
+ assert_equal :failed, @cart_storage_engine.state
65
+ end
66
+ end
67
+ end
68
+
10
69
  context 'sub_total' do
11
70
  setup do
12
71
  @item_1 = TestItem.new(1)
@@ -8,6 +8,50 @@ class CartTest < Test::Unit::TestCase
8
8
  @cart = ActiveCart::Cart.new(@cart_storage_engine)
9
9
  end
10
10
 
11
+ context 'callbacks' do
12
+ should 'fire the item before_add_to_cart callback on add to cart' do
13
+ item = TestItem.new
14
+ item.expects(:before_add_to_cart).with(1).returns(true)
15
+ @cart.add_to_cart(item, 1)
16
+ assert_equal 1, @cart.quantity
17
+ end
18
+
19
+ should 'halt and return false if beforee_add_to_cart returnds false' do
20
+ item = TestItem.new
21
+ item.expects(:before_add_to_cart).with(1).returns(false)
22
+ assert !@cart.add_to_cart(item, 1)
23
+ assert_equal 0, @cart.quantity
24
+ end
25
+
26
+ should 'fire the item after_add_to_cart callback' do
27
+ item = TestItem.new
28
+ item.expects(:after_add_to_cart).with(1)
29
+ @cart.add_to_cart(item, 1)
30
+ end
31
+
32
+ should 'fire the item before_remove_from_cart callback on add to cart' do
33
+ item = TestItem.new
34
+ item.expects(:before_remove_from_cart).with(1).returns(true)
35
+ @cart.remove_from_cart(item, 1)
36
+ assert_equal 0, @cart.quantity
37
+ end
38
+
39
+ should 'halt and return false if beforee_add_to_cart returns false' do
40
+ item = TestItem.new
41
+ @cart.add_to_cart(item, 1)
42
+ assert_equal 1, @cart.quantity
43
+ item.expects(:before_remove_from_cart).with(1).returns(false)
44
+ assert !@cart.remove_from_cart(item, 1)
45
+ assert_equal 1, @cart.quantity
46
+ end
47
+
48
+ should 'fire the item after_remove_from_cart callback' do
49
+ item = TestItem.new
50
+ item.expects(:after_remove_from_cart).with(1)
51
+ @cart.remove_from_cart(item, 1)
52
+ end
53
+ end
54
+
11
55
  context 'delegate to cart storage' do
12
56
  should 'delegate []' do
13
57
  @cart_storage_engine.expects(:[]).with(0)
@@ -141,6 +185,36 @@ class CartTest < Test::Unit::TestCase
141
185
  @cart_storage_engine.expects(:sub_total)
142
186
  @cart.sub_total
143
187
  end
188
+
189
+ should 'delegate :state' do
190
+ @cart_storage_engine.expects(:state).returns(:shopping)
191
+ @cart.state
192
+ end
193
+
194
+ should 'delegate :continue_shopping!' do
195
+ @cart_storage_engine.expects(:continue_shopping!)
196
+ @cart.continue_shopping!
197
+ end
198
+
199
+ should 'delegate :checkout!' do
200
+ @cart_storage_engine.expects(:checkout!)
201
+ @cart.checkout!
202
+ end
203
+
204
+ should 'delegate :check_payment!' do
205
+ @cart_storage_engine.expects(:check_payment!)
206
+ @cart.check_payment!
207
+ end
208
+
209
+ should 'delegate :payment_successful!' do
210
+ @cart_storage_engine.expects(:payment_successful!)
211
+ @cart.payment_successful!
212
+ end
213
+
214
+ should 'delegate :payment_failed!' do
215
+ @cart_storage_engine.expects(:payment_failed!)
216
+ @cart.payment_failed!
217
+ end
144
218
  end
145
219
 
146
220
  context 'setup' do
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+
3
+ class ItemTest < Test::Unit::TestCase
4
+ context '' do
5
+ should '' do
6
+ assert true
7
+ end
8
+ end
9
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_cart
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Myles Eftos
@@ -11,8 +11,17 @@ cert_chain: []
11
11
 
12
12
  date: 2010-02-27 00:00:00 +08:00
13
13
  default_executable:
14
- dependencies: []
15
-
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: aasm
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
16
25
  description: You can use active-cart as the basis of a shopping cart system. It's definately not complete, you need to build a website around it.
17
26
  email: myles@madpilot.com.au
18
27
  executables: []
@@ -32,6 +41,7 @@ files:
32
41
  - lib/active_cart.rb
33
42
  - lib/active_cart/cart.rb
34
43
  - lib/active_cart/cart_storage.rb
44
+ - lib/active_cart/item.rb
35
45
  - lib/active_cart/order_total.rb
36
46
  - lib/active_cart/order_total_collection.rb
37
47
  - test/mocks/test_cart_storage.rb
@@ -41,6 +51,7 @@ files:
41
51
  - test/test_helper.rb
42
52
  - test/unit/cart_storage_test.rb
43
53
  - test/unit/cart_test.rb
54
+ - test/unit/item_test.rb
44
55
  - test/unit/order_total_collection_test.rb
45
56
  - uninstall.rb
46
57
  has_rdoc: true
@@ -75,6 +86,7 @@ test_files:
75
86
  - test/unit/order_total_collection_test.rb
76
87
  - test/unit/cart_storage_test.rb
77
88
  - test/unit/cart_test.rb
89
+ - test/unit/item_test.rb
78
90
  - test/mocks/test_cart_storage.rb
79
91
  - test/mocks/test_order_total.rb
80
92
  - test/mocks/test_item.rb