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 +1 -1
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/lib/active_cart/cart.rb +45 -1
- data/lib/active_cart/cart_storage.rb +107 -1
- data/lib/active_cart/item.rb +36 -0
- data/lib/active_cart.rb +3 -0
- data/test/mocks/test_item.rb +16 -3
- data/test/unit/cart_storage_test.rb +59 -0
- data/test/unit/cart_test.rb +74 -0
- data/test/unit/item_test.rb +9 -0
- metadata +15 -3
data/.gitignore
CHANGED
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.2
|
data/lib/active_cart/cart.rb
CHANGED
@@ -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
|
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'
|
data/test/mocks/test_item.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
class TestItem
|
2
|
-
|
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
|
-
|
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
|
-
"
|
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)
|
data/test/unit/cart_test.rb
CHANGED
@@ -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
|
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.
|
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
|