galvinhsiu-active_cart 0.0.19
Sign up to get free protection for your applications and to get access to all the features.
- data/galvinhsiu-active_cart.gemspec +21 -0
- data/lib/active_cart/cart.rb +189 -0
- data/lib/active_cart/cart_storage.rb +121 -0
- data/lib/active_cart/item.rb +36 -0
- data/lib/active_cart/items/memory_item.rb +18 -0
- data/lib/active_cart/order_total.rb +48 -0
- data/lib/active_cart/order_total_collection.rb +75 -0
- data/lib/active_cart/storage_engines/memory.rb +14 -0
- data/lib/active_cart.rb +20 -0
- data/lib/exceptions/out_of_stock.rb +4 -0
- metadata +115 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'galvinhsiu-active_cart'
|
3
|
+
s.summary = "Shopping Cart framework gem. Supports 'storage engines' and order total plugins, forked to use state_machine from active_cart."
|
4
|
+
s.description = <<-EOS
|
5
|
+
You can use active_cart as the basis of a shopping cart system. It's not a shopping cart application - it's a shopping cart framework.
|
6
|
+
EOS
|
7
|
+
s.email = 'myles@madpilot.com.au, galvinhsiu@gmail.com'
|
8
|
+
s.homepage = "http://gemcutter.org/gems/galvinhsiu-active_cart"
|
9
|
+
s.authors = ["Myles Eftos", "Galvin Hsiu"]
|
10
|
+
s.version = '0.0.19'
|
11
|
+
|
12
|
+
s.require_paths = ['lib']
|
13
|
+
|
14
|
+
s.add_dependency 'state_machine'
|
15
|
+
s.add_development_dependency 'shoulda'
|
16
|
+
s.add_development_dependency 'mocha'
|
17
|
+
s.add_development_dependency 'machinist'
|
18
|
+
s.add_development_dependency 'faker'
|
19
|
+
|
20
|
+
s.files = Dir['lib/**/*', 'galvinhsiu-active_cart.gemspec']
|
21
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
module ActiveCart
|
2
|
+
# The Cart class is the core class in ActiveCart. It is a singleton (so you can only have one cart per application), that gets setup initially by passing in
|
3
|
+
# a storage engine instance. Storage engines abstract away the storage of the cart, and is left as an exercise to the user. See the Storage engine docs
|
4
|
+
# for details.
|
5
|
+
#
|
6
|
+
# The Cart class also takes order_total objects, which will calculate order totals. it may include thinkgs like shipping, or gift vouchers etc. See the Order Total
|
7
|
+
# docs for details.
|
8
|
+
#
|
9
|
+
# The Cart object delegates a number of Array methods:
|
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
|
+
#
|
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
|
+
#
|
21
|
+
class Cart
|
22
|
+
attr_accessor :storage_engine, :order_total_calculators, :customer
|
23
|
+
include Enumerable
|
24
|
+
|
25
|
+
# You need to supply a storage engine. An optional block can be given which allows you to add order total items.
|
26
|
+
#
|
27
|
+
# A typical initialization block might look like this
|
28
|
+
#
|
29
|
+
# @cart = Cart.new(MyAwesomeStorageEngine.new) do |o|
|
30
|
+
# o << ShippingOrderTotal.new
|
31
|
+
# o << GstOrderTotal.new
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
def initialize(storage_engine, &block)
|
35
|
+
@storage_engine = storage_engine
|
36
|
+
@order_total_calculators = OrderTotalCollection.new(self)
|
37
|
+
|
38
|
+
if block_given?
|
39
|
+
yield order_total_calculators
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
extend Forwardable
|
44
|
+
# Storage Engine Array delegators
|
45
|
+
# TODO: Consolidate - not all of these make sense, and some need overriding so they take the callbacks into account
|
46
|
+
#
|
47
|
+
# Candidates for removal:
|
48
|
+
# delete_at
|
49
|
+
# delete_if
|
50
|
+
# []=
|
51
|
+
# replace
|
52
|
+
# insert
|
53
|
+
# shift
|
54
|
+
# unshift
|
55
|
+
# pop
|
56
|
+
#
|
57
|
+
# Aliases
|
58
|
+
# delete => remove_from_cart(obj, 1)
|
59
|
+
# << => add_to_cart(obj, 1)
|
60
|
+
# push => add_to_cart(obj, 1)
|
61
|
+
#
|
62
|
+
# Overrides
|
63
|
+
# clear => .each { |obj| self.remove_from_cart(obj) }
|
64
|
+
#
|
65
|
+
def_delegators :@storage_engine, :[], :<<, :[]=, :at, :clear, :collect, :map, :delete, :delete_at, :each, :each_index, :empty?, :eql?, :first, :include?, :index, :inject, :last, :length, :pop, :push, :shift, :size, :unshift
|
66
|
+
|
67
|
+
# Returns a unique id for the invoice. It's upto the storage engine to generate and track these numbers
|
68
|
+
#
|
69
|
+
def invoice_id
|
70
|
+
@storage_engine.invoice_id
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns the sub-total of all the items in the cart. Usually returns a float.
|
74
|
+
#
|
75
|
+
# @cart.sub_total # => 100.00
|
76
|
+
#
|
77
|
+
def quantity
|
78
|
+
@storage_engine.quantity
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns the subtotal of cart, which is effectively the total of all the items multiplied by their quantites. Does NOT include order_totals
|
82
|
+
def sub_total
|
83
|
+
@storage_engine.sub_total
|
84
|
+
end
|
85
|
+
|
86
|
+
# Adds an item to the cart. If the item already exists in the cart (identified by the id of the item), then the quantity will be increased but the supplied quantity (default: 1)
|
87
|
+
#
|
88
|
+
# @cart.add_to_cart(item, 5)
|
89
|
+
# @cart.quantity # => 5
|
90
|
+
#
|
91
|
+
# @cart.add_to_cart(item, 2)
|
92
|
+
# @cart.quantity # => 7
|
93
|
+
# @cart[0].size # => 7
|
94
|
+
# @cart[1] # => nil
|
95
|
+
#
|
96
|
+
# @cart.add_to_cart(item_2, 4)
|
97
|
+
# @cart.quantity => 100
|
98
|
+
# @cart[0].size # => 7
|
99
|
+
# @cart[1].size # => 4
|
100
|
+
#
|
101
|
+
# Callbacks:
|
102
|
+
#
|
103
|
+
# Calls the storage engines before_add_to_cart(item, quantity) and after_add_to_cart(item, quantity) methods (if they exist). If before_add_to_cart returns false, the add will be halted.
|
104
|
+
# Calls the items before_add_to_item(quantity) and after_add_to_cart(quantity) methods (if they exist). If before_add_to_cart returns false, the add will be halted.
|
105
|
+
#
|
106
|
+
def add_to_cart(item, quantity = 1, options = {})
|
107
|
+
with_callbacks(:add_to_cart, item, quantity, options) do
|
108
|
+
@storage_engine.add_to_cart(item, quantity, options)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Sets the quantity of an item to the supplied value
|
113
|
+
#
|
114
|
+
# @cart.add_to_cart(item, 5)
|
115
|
+
# @cart[0].quantity # => 5
|
116
|
+
# @cart.update_cart(item, 15)
|
117
|
+
# @cart[0].quantity # => 15
|
118
|
+
# @cart.update_cart(item, 2)
|
119
|
+
# @cart[0].quantity # => 2
|
120
|
+
#
|
121
|
+
def update_cart(item, quantity = 1, options = {})
|
122
|
+
if @storage_engine.include?(item)
|
123
|
+
index = @storage_engine.index(item)
|
124
|
+
diff = quantity - @storage_engine.at(index).quantity
|
125
|
+
|
126
|
+
if diff < 0
|
127
|
+
return remove_from_cart(item, -1 * diff, options)
|
128
|
+
else
|
129
|
+
return add_to_cart(item, diff, options)
|
130
|
+
end
|
131
|
+
else
|
132
|
+
return add_to_cart(item, quantity, options)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# 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
|
137
|
+
#
|
138
|
+
# @cart.add_to_cart(item, 5)
|
139
|
+
# @cart[0].quantity # => 5
|
140
|
+
# @cart.remove_from_cart(item, 3)
|
141
|
+
# @cart[0].quantity # => 2
|
142
|
+
# @cart.remove_from_cart(item, 2)
|
143
|
+
# @cart[0] # => nil
|
144
|
+
#
|
145
|
+
# Callbacks:
|
146
|
+
#
|
147
|
+
# Calls the storage engines before_remove_from_cart(item, quantity) and after_remove_from_cart(item, quantity) methods (if they exist). If before_remove_from_cart returns false, the remove will be halted.
|
148
|
+
# Calls the items before_remove_from_item(quantity) and after_remove_from_cart(quantity) methods (if they exist). If before_remove_from_cart returns false, the remove will be halted.
|
149
|
+
#
|
150
|
+
def remove_from_cart(item, quantity = 1, options = {})
|
151
|
+
with_callbacks(:remove_from_cart, item, quantity, options) do
|
152
|
+
@storage_engine.remove_from_cart(item, quantity, options)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Returns the total of the cart. This includes all the order_total calculations
|
157
|
+
#
|
158
|
+
def total
|
159
|
+
sub_total + order_total_calculators.total
|
160
|
+
end
|
161
|
+
|
162
|
+
# Returns the current state of the cart storage engine
|
163
|
+
#
|
164
|
+
def state
|
165
|
+
storage_engine.state
|
166
|
+
end
|
167
|
+
|
168
|
+
# :nodoc
|
169
|
+
def method_missing(symbol, *args)
|
170
|
+
# This allows developers to add extra aasm event transaction, and still allow them to called from the cart
|
171
|
+
if @storage_engine.state_paths.events.include?(symbol.to_s[0..-2].to_sym)
|
172
|
+
@storage_engine.send(symbol)
|
173
|
+
else
|
174
|
+
super
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
def with_callbacks(type, item, quantity, options, &blk)
|
180
|
+
return false unless item.send("before_#{type}".to_sym, quantity, options) if item.respond_to?("before_#{type}".to_sym)
|
181
|
+
return false unless @storage_engine.send("before_#{type}".to_sym, item, quantity, options) if @storage_engine.respond_to?("before_#{type}".to_sym)
|
182
|
+
|
183
|
+
yield
|
184
|
+
|
185
|
+
@storage_engine.send("after_#{type}".to_sym, item, quantity, options) if @storage_engine.respond_to?("after_#{type}".to_sym)
|
186
|
+
item.send("after_#{type}".to_sym, quantity, options) if item.respond_to?("after_#{type}".to_sym)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# Mixin this module into the class you want to use as your storage class. Remember to override the invoice_id method
|
2
|
+
#
|
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, verifying_payment or failed 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, verifying_payment or failed to shopping
|
12
|
+
#
|
13
|
+
# It will fire before_ and after callbacks with the same name as the transitions
|
14
|
+
#
|
15
|
+
module CartStorage
|
16
|
+
def self.included(base) #:nodoc:
|
17
|
+
|
18
|
+
base.state_machine :state, :initial => :shopping do
|
19
|
+
|
20
|
+
event :continue_shopping do
|
21
|
+
transition [ :checkout, :verifying_payment, :failed ] => :shopping
|
22
|
+
end
|
23
|
+
|
24
|
+
event :checkout do
|
25
|
+
transition [ :shopping, :verifying_payment, :failed ] => :checkout
|
26
|
+
end
|
27
|
+
|
28
|
+
event :check_payment do
|
29
|
+
transition :checkout => :verifying_payment
|
30
|
+
end
|
31
|
+
|
32
|
+
event :payment_successful do
|
33
|
+
transition :verifying_payment => :completed
|
34
|
+
end
|
35
|
+
|
36
|
+
event :payment_failed do
|
37
|
+
transition :verifying_payment => :failed
|
38
|
+
end
|
39
|
+
|
40
|
+
state :shopping
|
41
|
+
state :checkout
|
42
|
+
state :verifying_payment
|
43
|
+
state :completed
|
44
|
+
state :failed
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the unique invoice_id for this cart instance. This MUST be overriden by the concrete class this module is mixed into, otherwise you
|
49
|
+
# will get a NotImplementedError
|
50
|
+
#
|
51
|
+
def invoice_id
|
52
|
+
raise NotImplementedError
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the sub-total of all the items in the cart. Usually returns a float.
|
56
|
+
#
|
57
|
+
# @cart.sub_total # => 100.00
|
58
|
+
#
|
59
|
+
def sub_total
|
60
|
+
inject(0) { |t, item| t + (item.quantity * item.price.to_f) }
|
61
|
+
end
|
62
|
+
|
63
|
+
# 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
|
64
|
+
#
|
65
|
+
def quantity
|
66
|
+
inject(0) { |t, item| t + item.quantity }
|
67
|
+
end
|
68
|
+
|
69
|
+
# Adds an item to the cart. If the item already exists in the cart (identified by the id of the item), then the quantity will be increased but the supplied quantity (default: 1)
|
70
|
+
#
|
71
|
+
# @cart.add_to_cart(item, 5)
|
72
|
+
# @cart.quantity # => 5
|
73
|
+
#
|
74
|
+
# @cart.add_to_cart(item, 2)
|
75
|
+
# @cart.quantity # => 7
|
76
|
+
# @cart[0].quantity # => 7
|
77
|
+
# @cart[1] # => nil
|
78
|
+
#
|
79
|
+
# @cart.add_to_cart(item_2, 4)
|
80
|
+
# @cart.quantity => 100
|
81
|
+
# @cart[0].quantity # => 7
|
82
|
+
# @cart[1].quantity # => 4
|
83
|
+
#
|
84
|
+
def add_to_cart(item, quantity = 1, options = {})
|
85
|
+
if self.include?(item)
|
86
|
+
index = self.index(item)
|
87
|
+
self.at(index).quantity += quantity
|
88
|
+
else
|
89
|
+
item.quantity += quantity
|
90
|
+
self << item
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# 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
|
95
|
+
#
|
96
|
+
# @cart.add_to_cart(item, 5)
|
97
|
+
# @cart[0].quantity # => 5
|
98
|
+
# @cart.remove_from_cart(item, 3)
|
99
|
+
# @cart[0].quantity # => 2
|
100
|
+
# @cart.remove_from_cart(item, 2)
|
101
|
+
# @cart[0] # => nil
|
102
|
+
#
|
103
|
+
# @cart.add_to_cart(item, 3)
|
104
|
+
# @cart[0].quantity # => 3
|
105
|
+
# @cart_remove_from_cart(item, :all)
|
106
|
+
# @cart[[0].quantity # => 0
|
107
|
+
def remove_from_cart(item, quantity = 1, option = {})
|
108
|
+
if self.include?(item)
|
109
|
+
index = self.index(item)
|
110
|
+
|
111
|
+
quantity = self.at(index).quantity if quantity == :all
|
112
|
+
|
113
|
+
if (existing = self.at(index)).quantity - quantity > 0
|
114
|
+
existing.quantity = existing.quantity - quantity
|
115
|
+
else
|
116
|
+
self.delete_at(index)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
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
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ActiveCart
|
2
|
+
module Items
|
3
|
+
class MemoryItem
|
4
|
+
attr_accessor :id, :name, :price
|
5
|
+
include ActiveCart::Item
|
6
|
+
|
7
|
+
def ==(item)
|
8
|
+
self.id == item.id
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(id, name, price)
|
12
|
+
@id = id
|
13
|
+
@name = name
|
14
|
+
@price = price
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# Mixin this module into any classes that act as an order_total.
|
2
|
+
#
|
3
|
+
# You need to overwrite the price, name and description methods
|
4
|
+
#
|
5
|
+
module ActiveCart
|
6
|
+
module OrderTotal
|
7
|
+
|
8
|
+
# Returns a boolean depending if the order total is active in this particular cart
|
9
|
+
#
|
10
|
+
# @order_total.active? # => true
|
11
|
+
#
|
12
|
+
def active?
|
13
|
+
@active || false
|
14
|
+
end
|
15
|
+
|
16
|
+
# Make a particular order_total active
|
17
|
+
#
|
18
|
+
# @order_total.active = true
|
19
|
+
# @order_total.active? # => true
|
20
|
+
#
|
21
|
+
# @order_total.active = false
|
22
|
+
# @order_total.active? # => falese
|
23
|
+
#
|
24
|
+
def active=(active)
|
25
|
+
@active = active
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the adjustment caused by this order_total object. Takes a cart object as a parameter, allowing the object to interrogate the items in the cart.
|
29
|
+
#
|
30
|
+
# This must be overriden in the mixee class, otherwise it will throw a NotImplementedError
|
31
|
+
#
|
32
|
+
# @order.price(@cart) => 2
|
33
|
+
#
|
34
|
+
def price(cart)
|
35
|
+
raise NotImplementedError
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the friendly name of the order total. Can be used for display (Such as on an invoices etc)
|
39
|
+
#
|
40
|
+
# This must be overriden in the mixee class, otherwise it will throw a NotImplementedError
|
41
|
+
#
|
42
|
+
# @order.name # => 'My awesome order total class'
|
43
|
+
#
|
44
|
+
def name
|
45
|
+
raise NotImplementedError
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module ActiveCart
|
2
|
+
class OrderTotalCollection < Array
|
3
|
+
# Returns a new OrderTotalCollection
|
4
|
+
#
|
5
|
+
# You must pass in an cart object.
|
6
|
+
#
|
7
|
+
# collection = OrderTotalCollection.new(@cart)
|
8
|
+
#
|
9
|
+
# You can also pass in an array of order_total objects
|
10
|
+
#
|
11
|
+
# cart = ActiveCart::Cart.instance
|
12
|
+
# collection = OrderTotalCollection.new(cart, order_total_1, order_total_2) # => [ order_total_1, order_total_2 ]
|
13
|
+
#
|
14
|
+
def initialize(cart, *seed)
|
15
|
+
@cart = cart
|
16
|
+
seed.each do |s|
|
17
|
+
self.push(s)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Concatenation.Returns a new OrderTotalCollection built by concatenating the two OrderTotalCollections together to produce a third OrderTotalCollection. (The supplied collection can be a regular array)
|
22
|
+
#
|
23
|
+
# [ order_total_1, order_total_2, order_total_3 ] + [ order_total_4, order_total_5 ] #=> [ order_total_1, order_total_2, order_total_3, order_total_4, order_total_5 ]
|
24
|
+
#
|
25
|
+
def +(order_total_collection)
|
26
|
+
tmp = OrderTotalCollection.new(@cart)
|
27
|
+
self.each { |s| tmp << s }
|
28
|
+
order_total_collection.each { |s| tmp << s }
|
29
|
+
tmp
|
30
|
+
end
|
31
|
+
|
32
|
+
# Inserts the items before the item that is currently at the supplied index
|
33
|
+
#
|
34
|
+
# items = [ order_total_1, order_total_2 ]
|
35
|
+
# items.insert_before(1, order_total_2) #=> [ order_total_1, order_total_3, order_total_2 ]
|
36
|
+
#
|
37
|
+
def insert_before(index, *items)
|
38
|
+
items.reverse.each do |item|
|
39
|
+
self.insert(index, item)
|
40
|
+
end
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
# Inserts the items after the item that is currently at the supplied index
|
45
|
+
#
|
46
|
+
# items = [ order_total_1, order_total_2 ]
|
47
|
+
# items.insert_after(0, order_total_2) #=> [ order_total_1, order_total_3, order_total_2 ]
|
48
|
+
#
|
49
|
+
def insert_after(index, *items)
|
50
|
+
items.each_with_index do |item, i|
|
51
|
+
self.insert(index + i + 1, item)
|
52
|
+
end
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
# Allows you to reorder the order totals. Moves the item at index <em>from</em> to index <em>to</em>
|
57
|
+
#
|
58
|
+
# items = [ order_total_1, order_total_2 ]
|
59
|
+
# items.move(0, 1) #=> [ order_total_2, order_total_1 ]
|
60
|
+
#
|
61
|
+
def move(from, to)
|
62
|
+
index = self.delete_at(from)
|
63
|
+
self.insert(to, index)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Calculates the total price applied by all the order_total objects.
|
67
|
+
#
|
68
|
+
# order_total_collection = OrderTotalCollection.new(nil, order_total_1, order_total_2)
|
69
|
+
# order_total_collection.total # => 10
|
70
|
+
#
|
71
|
+
def total
|
72
|
+
self.inject(0) { |t, calculator| t + (calculator.active? ? calculator.price(@cart) : 0) }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ActiveCart
|
2
|
+
module StorageEngines
|
3
|
+
# This storage engine is probably only useful as a reference implementation (It would work in a desktop app I guess). Items only exist in memory, so
|
4
|
+
# as soon as the thread dies, so does the cart.
|
5
|
+
#
|
6
|
+
class Memory < Array
|
7
|
+
include ActiveCart::CartStorage
|
8
|
+
|
9
|
+
def invoice_id
|
10
|
+
@@last_id = @@last_id ? @@last_id + 1 : 1
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/active_cart.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), 'active_cart'))
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'singleton'
|
5
|
+
require 'forwardable'
|
6
|
+
require 'state_machine'
|
7
|
+
require 'item'
|
8
|
+
require 'cart_storage'
|
9
|
+
require 'order_total'
|
10
|
+
require 'order_total_collection'
|
11
|
+
require 'cart'
|
12
|
+
|
13
|
+
require 'exceptions/out_of_stock'
|
14
|
+
|
15
|
+
require 'storage_engines/memory'
|
16
|
+
require 'items/memory_item'
|
17
|
+
|
18
|
+
module ActiveCart
|
19
|
+
VERSION = File.exist?('VERSION') ? File.read('VERSION') : ""
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: galvinhsiu-active_cart
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.19
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Myles Eftos
|
9
|
+
- Galvin Hsiu
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2011-12-07 00:00:00.000000000Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: state_machine
|
17
|
+
requirement: &86400220 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *86400220
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: shoulda
|
28
|
+
requirement: &86400000 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *86400000
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: mocha
|
39
|
+
requirement: &86399790 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
type: :development
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *86399790
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: machinist
|
50
|
+
requirement: &86399580 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *86399580
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: faker
|
61
|
+
requirement: &86399370 !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
type: :development
|
68
|
+
prerelease: false
|
69
|
+
version_requirements: *86399370
|
70
|
+
description: ! ' You can use active_cart as the basis of a shopping cart system.
|
71
|
+
It''s not a shopping cart application - it''s a shopping cart framework.
|
72
|
+
|
73
|
+
'
|
74
|
+
email: myles@madpilot.com.au, galvinhsiu@gmail.com
|
75
|
+
executables: []
|
76
|
+
extensions: []
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- lib/active_cart.rb
|
80
|
+
- lib/exceptions/out_of_stock.rb
|
81
|
+
- lib/active_cart/cart.rb
|
82
|
+
- lib/active_cart/order_total_collection.rb
|
83
|
+
- lib/active_cart/cart_storage.rb
|
84
|
+
- lib/active_cart/items/memory_item.rb
|
85
|
+
- lib/active_cart/order_total.rb
|
86
|
+
- lib/active_cart/storage_engines/memory.rb
|
87
|
+
- lib/active_cart/item.rb
|
88
|
+
- galvinhsiu-active_cart.gemspec
|
89
|
+
homepage: http://gemcutter.org/gems/galvinhsiu-active_cart
|
90
|
+
licenses: []
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ! '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ! '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 1.8.12
|
110
|
+
signing_key:
|
111
|
+
specification_version: 3
|
112
|
+
summary: Shopping Cart framework gem. Supports 'storage engines' and order total plugins,
|
113
|
+
forked to use state_machine from active_cart.
|
114
|
+
test_files: []
|
115
|
+
has_rdoc:
|