cartman 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +22 -1
- data/lib/cartman.rb +0 -1
- data/lib/cartman/cart.rb +60 -8
- data/lib/cartman/configuration.rb +1 -0
- data/lib/cartman/version.rb +1 -1
- data/spec/cart_spec.rb +106 -0
- metadata +4 -4
data/README.md
CHANGED
@@ -28,6 +28,7 @@ Cartman has a few (read 3) configuration options you can set, most likely in an
|
|
28
28
|
Cartman.config do
|
29
29
|
cart_expires_in 604800 # one week, in seconds. This is the default
|
30
30
|
cost_field :cost # for cart totaling
|
31
|
+
quantity_field :quantity # for quantity totaling
|
31
32
|
redis Redis.new # set the redis connection here
|
32
33
|
end
|
33
34
|
```
|
@@ -47,6 +48,22 @@ The returned Items come back as `Cartman::Item` instances, which have a few spec
|
|
47
48
|
- `_key` - which will return the redis key the data is stored in. Probably won't need that, but it's there.
|
48
49
|
- `#{attribute}=` - this is a setter defined for all of the items attributes that you gave it. It will instantly save to redis also, so no need to call `save` (which is why there isn't a `save`).
|
49
50
|
|
51
|
+
The `Cart` object also has some handy methods that you should be aware of:
|
52
|
+
|
53
|
+
- `add_item(data)` - which is the life blood of Cartman. This method takes a hash of data you would like to store with the item. Here's a few suggestions of keys you may want in your hash:
|
54
|
+
- `:id` - to store the ID of the product you're adding
|
55
|
+
- `:type` - to store the class of the product you're adding. Useful if you have multiple models that can go in the cart.
|
56
|
+
- `:cost` - which if you use will let you use the `Cart#total` method without any extra configuration
|
57
|
+
- `:quantity` - which if you use will let you use the `Cart#quantity` method without any extra configuration
|
58
|
+
- `remove_item(item) - which, you guessed it, removes an item. This method takes an Item object, not a hash.
|
59
|
+
- `contains?(Product)` - This is a biggie. It will tell you if a certain item is in the cart. And the way it works is you pass it an object, like an instance of a Product model, and it will examine the class, and the id, and look to see if it's in the cart already. This method only works if the `:id` and `:type` keys are set in the item's data hash.
|
60
|
+
- `count` - which will give you the total number of items in the cart. Faster than `cart.items.size` because it doesn't load all of the item data from redis.
|
61
|
+
- `quantity` - which will return the total quantity of all the items. The quantity field is set in the config block, by default it's :quantity
|
62
|
+
- `ttl` - will tell you how many seconds until the cart expires. It will return -1 if the cart will never expire
|
63
|
+
- `touch` - which will reset the ttl back to whatever expiration length is set in the config. Touch is automatically called after `add_item` and `remove_item`
|
64
|
+
- `destroy!` - which will delete the cart, and all the line_items out of it
|
65
|
+
- `reassign(id)` - this method will reassign the cart's unique identifier. So to access this cart at a later time after reassigning, you would put `Cart.new(reassigned_id)`. This is useful for having a cart for an unsigned in user. You can use their session ID while they're unauthenticated, and then when they sign in, you can reassign the cart to the user's ID. NOTE: Reassigning will overwrite any cart in it's way.
|
66
|
+
|
50
67
|
Lets walk through an example implementation with a Rails app that has a User model and a Product model.
|
51
68
|
|
52
69
|
```ruby
|
@@ -58,7 +75,9 @@ class User < ActiveRecord::Base
|
|
58
75
|
end
|
59
76
|
#...
|
60
77
|
end
|
78
|
+
```
|
61
79
|
|
80
|
+
```ruby
|
62
81
|
# app/controllers/products_controller.rb
|
63
82
|
class ProductsController < ApplicationController
|
64
83
|
#...
|
@@ -69,8 +88,10 @@ class ProductsController < ApplicationController
|
|
69
88
|
end
|
70
89
|
#...
|
71
90
|
end
|
91
|
+
```
|
72
92
|
|
73
|
-
|
93
|
+
```haml
|
94
|
+
-# app/view/cart/show.html.haml
|
74
95
|
%h1 Cart - Total: #{@cart.total}
|
75
96
|
%ul
|
76
97
|
- @cart.items.each do |item|
|
data/lib/cartman.rb
CHANGED
data/lib/cartman/cart.rb
CHANGED
@@ -11,19 +11,21 @@ module Cartman
|
|
11
11
|
line_item_id = @@redis.incr CART_LINE_ITEM_ID_KEY
|
12
12
|
@@redis.mapped_hmset("cartman:line_item:#{line_item_id}", options)
|
13
13
|
@@redis.sadd key, line_item_id
|
14
|
-
|
15
|
-
|
16
|
-
@@redis.pipelined do
|
17
|
-
keys_to_expire.each do |item|
|
18
|
-
@@redis.expire item, Cartman::Configuration.cart_expires_in
|
19
|
-
end
|
14
|
+
if options.has_key?(:id) && options.has_key?(:type)
|
15
|
+
@@redis.sadd index_key, "#{options[:type]}:#{options[:id]}"
|
20
16
|
end
|
17
|
+
touch
|
21
18
|
get_item(line_item_id)
|
22
19
|
end
|
23
20
|
|
24
21
|
def remove_item(item)
|
25
22
|
@@redis.del "cartman:line_item:#{item._id}"
|
26
23
|
@@redis.srem key, item._id
|
24
|
+
begin
|
25
|
+
@@redis.srem index_key, "#{item.type}:#{item.id}"
|
26
|
+
rescue KeyError
|
27
|
+
end
|
28
|
+
touch
|
27
29
|
end
|
28
30
|
|
29
31
|
def items
|
@@ -32,6 +34,20 @@ module Cartman
|
|
32
34
|
}
|
33
35
|
end
|
34
36
|
|
37
|
+
def contains?(object)
|
38
|
+
@@redis.sismember index_key, "#{object.class}:#{object.id}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def count
|
42
|
+
@@redis.scard key
|
43
|
+
end
|
44
|
+
|
45
|
+
def quantity
|
46
|
+
line_item_keys.collect { |item_key|
|
47
|
+
@@redis.hget item_key, Cartman::Configuration.quantity_field
|
48
|
+
}.inject(0){|sum,quantity| sum += quantity.to_i}
|
49
|
+
end
|
50
|
+
|
35
51
|
def total
|
36
52
|
items.collect { |item|
|
37
53
|
(item.send(Cartman::Configuration.cost_field).to_f * 100).to_i
|
@@ -42,10 +58,46 @@ module Cartman
|
|
42
58
|
@@redis.ttl key
|
43
59
|
end
|
44
60
|
|
61
|
+
def destroy!
|
62
|
+
keys = line_item_keys
|
63
|
+
keys << key
|
64
|
+
keys << index_key
|
65
|
+
@@redis.pipelined do
|
66
|
+
keys.each do |key|
|
67
|
+
@@redis.del key
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def touch
|
73
|
+
keys_to_expire = line_item_keys
|
74
|
+
keys_to_expire << key
|
75
|
+
keys_to_expire << index_key
|
76
|
+
@@redis.pipelined do
|
77
|
+
keys_to_expire.each do |item|
|
78
|
+
@@redis.expire item, Cartman::Configuration.cart_expires_in
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def reassign(new_id)
|
84
|
+
if @@redis.exists key
|
85
|
+
@@redis.rename key, key(new_id)
|
86
|
+
end
|
87
|
+
if @@redis.exists index_key
|
88
|
+
@@redis.rename index_key, index_key(new_id)
|
89
|
+
end
|
90
|
+
@uid = new_id
|
91
|
+
end
|
92
|
+
|
45
93
|
private
|
46
94
|
|
47
|
-
def key
|
48
|
-
"cartman:cart:#{
|
95
|
+
def key(id=@uid)
|
96
|
+
"cartman:cart:#{id}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def index_key(id=@uid)
|
100
|
+
key(id) + ":index"
|
49
101
|
end
|
50
102
|
|
51
103
|
def line_item_ids
|
data/lib/cartman/version.rb
CHANGED
data/spec/cart_spec.rb
CHANGED
@@ -32,6 +32,17 @@ describe Cartman do
|
|
32
32
|
Cartman.config.redis.ttl("cartman:line_item:1").should eq(Cartman.config.cart_expires_in)
|
33
33
|
end
|
34
34
|
|
35
|
+
it "should add an index key to be able to look up by type and ID" do
|
36
|
+
Cartman.config.redis.exists("cartman:cart:1:index").should be_true
|
37
|
+
Cartman.config.redis.sismember("cartman:cart:1:index", "Bottle:17").should be_true
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should not add an index key if type and ID are not set" do
|
41
|
+
cart.add_item(id: 18, name: "Cordeux", unit_cost: 92.12, cost: 184.24, quantity: 2)
|
42
|
+
Cartman.config.redis.sismember("cartman:cart:1:index", "Bottle:18").should be_false
|
43
|
+
Cartman.config.redis.scard("cartman:cart:1:index").should eq(1)
|
44
|
+
end
|
45
|
+
|
35
46
|
it "should return an Item" do
|
36
47
|
item = cart.add_item(id: 34, type: "Bottle", name: "Cabernet", unit_cost: 92.12, cost: 184.24, quantity: 2)
|
37
48
|
item.class.should eq(Cartman::Item)
|
@@ -61,6 +72,57 @@ describe Cartman do
|
|
61
72
|
end
|
62
73
|
end
|
63
74
|
|
75
|
+
describe "#contains?(item)" do
|
76
|
+
before(:all) do
|
77
|
+
Bottle = Struct.new(:id)
|
78
|
+
end
|
79
|
+
|
80
|
+
before(:each) do
|
81
|
+
cart.add_item(id: 17, type: "Bottle", name: "Bordeux", unit_cost: 92.12, cost: 184.24, quantity: 2)
|
82
|
+
cart.add_item(id: 34, name: "Cabernet", unit_cost: 92.12, cost: 184.24, quantity: 2)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should be able to tell you that an item in the cart is present" do
|
86
|
+
cart.contains?(Bottle.new(17)).should be_true
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should be able to tell you that an item in the cart is absent" do
|
90
|
+
cart.contains?(Bottle.new(20)).should be_false
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should be able to tell you that an item in the cart is absent if it's been removed" do
|
94
|
+
cart.remove_item(cart.items.first)
|
95
|
+
cart.contains?(Bottle.new(17)).should be_false
|
96
|
+
cart.remove_item(cart.items.last)
|
97
|
+
cart.contains?(Bottle.new(34)).should be_false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "#count" do
|
102
|
+
it "should return the number of items in the cart" do
|
103
|
+
cart.add_item(id: 17, type: "Bottle", name: "Bordeux", unit_cost: 92.12, cost: 184.24, quantity: 2)
|
104
|
+
cart.add_item(id: 34, type: "Bottle", name: "Cabernet", unit_cost: 92.12, cost: 184.24, quantity: 2)
|
105
|
+
cart.count.should eq(2)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "#quantity" do
|
110
|
+
it "should return the sum of the default quantity field" do
|
111
|
+
cart.add_item(id: 17, type: "Bottle", name: "Bordeux", unit_cost: 92.12, cost: 184.24, quantity: 2)
|
112
|
+
cart.add_item(id: 34, type: "Bottle", name: "Cabernet", unit_cost: 92.12, cost: 184.24, quantity: 2)
|
113
|
+
cart.quantity.should eq(4)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should return the sum of the defined quantity field" do
|
117
|
+
Cartman.config do
|
118
|
+
quantity_field :qty
|
119
|
+
end
|
120
|
+
cart.add_item(id: 17, type: "Bottle", name: "Bordeux", unit_cost: 92.12, cost: 184.24, qty: 2)
|
121
|
+
cart.add_item(id: 34, type: "Bottle", name: "Cabernet", unit_cost: 92.12, cost: 184.24, qty: 2)
|
122
|
+
cart.quantity.should eq(4)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
64
126
|
describe "#total" do
|
65
127
|
it "should total the default costs field" do
|
66
128
|
cart.add_item(id: 17, type: "Bottle", name: "Bordeux", unit_cost: 92.12, cost: 184.24, quantity: 2)
|
@@ -77,5 +139,49 @@ describe Cartman do
|
|
77
139
|
cart.total.should eq(36848)
|
78
140
|
end
|
79
141
|
end
|
142
|
+
|
143
|
+
describe "#destroy" do
|
144
|
+
it "should delete the line_item keys, the index key, and the cart key" do
|
145
|
+
cart.add_item(id: 17, type: "Bottle", name: "Bordeux", unit_cost: 92.12, cost_in_cents: 18424, quantity: 2)
|
146
|
+
cart.add_item(id: 34, type: "Bottle", name: "Cabernet", unit_cost: 92.12, cost_in_cents: 18424, quantity: 2)
|
147
|
+
cart.destroy!
|
148
|
+
Cartman.config.redis.exists("cartman:cart:1").should be_false
|
149
|
+
Cartman.config.redis.exists("cartman:line_item:1").should be_false
|
150
|
+
Cartman.config.redis.exists("cartman:line_item:2").should be_false
|
151
|
+
Cartman.config.redis.exists("cartman:cart:1:index").should be_false
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "#touch" do
|
156
|
+
it "should reset the TTL" do
|
157
|
+
cart.add_item(id: 17, type: "Bottle", name: "Bordeux", unit_cost: 92.12, cost_in_cents: 18424, quantity: 2)
|
158
|
+
cart.touch
|
159
|
+
cart.ttl.should eq(Cartman.config.cart_expires_in)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe "#reassign" do
|
164
|
+
it "should only change the @uid if no key exists" do
|
165
|
+
cart.reassign(2)
|
166
|
+
cart.send(:key)[-1].should eq("2")
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should rename the key, and index_key if it exists" do
|
170
|
+
cart.add_item(id: 17, name: "Bordeux", unit_cost: 92.12, cost_in_cents: 18424, quantity: 2)
|
171
|
+
cart.reassign(2)
|
172
|
+
Cartman.config.redis.exists("cartman:cart:1").should be_false
|
173
|
+
Cartman.config.redis.exists("cartman:cart:1:index").should be_false
|
174
|
+
Cartman.config.redis.exists("cartman:cart:2").should be_true
|
175
|
+
Cartman.config.redis.exists("cartman:cart:2:index").should be_false
|
176
|
+
cart.send(:key)[-1].should eq("2")
|
177
|
+
cart.add_item(id: 18, type: "Bottle", name: "Bordeux", unit_cost: 92.12, cost_in_cents: 18424, quantity: 2)
|
178
|
+
cart.reassign(1)
|
179
|
+
Cartman.config.redis.exists("cartman:cart:2").should be_false
|
180
|
+
Cartman.config.redis.exists("cartman:cart:2:index").should be_false
|
181
|
+
Cartman.config.redis.exists("cartman:cart:1").should be_true
|
182
|
+
Cartman.config.redis.exists("cartman:cart:1:index").should be_true
|
183
|
+
cart.send(:key)[-1].should eq("1")
|
184
|
+
end
|
185
|
+
end
|
80
186
|
end
|
81
187
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cartman
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
12
|
+
date: 2012-10-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
@@ -79,7 +79,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
79
79
|
version: '0'
|
80
80
|
segments:
|
81
81
|
- 0
|
82
|
-
hash:
|
82
|
+
hash: 1475251034207197325
|
83
83
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
84
|
none: false
|
85
85
|
requirements:
|
@@ -88,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
88
|
version: '0'
|
89
89
|
segments:
|
90
90
|
- 0
|
91
|
-
hash:
|
91
|
+
hash: 1475251034207197325
|
92
92
|
requirements: []
|
93
93
|
rubyforge_project:
|
94
94
|
rubygems_version: 1.8.23
|