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 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
- # app/view/cart/show.html.haml
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
@@ -3,7 +3,6 @@ require "cartman/configuration"
3
3
  require "cartman/cart"
4
4
  require "cartman/item"
5
5
  require 'redis'
6
- require 'ostruct'
7
6
 
8
7
  module Cartman
9
8
  module_function
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
- keys_to_expire = line_item_keys
15
- keys_to_expire << key
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:#{@uid}"
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
@@ -5,6 +5,7 @@ module Cartman
5
5
  @@configuration = {
6
6
  cart_expires_in: 604800, # one week
7
7
  cost_field: :cost, # for cart totaling
8
+ quantity_field: :quantity, # for cart totaling
8
9
  redis: Redis.new, # Redis connection
9
10
  }
10
11
 
@@ -1,3 +1,3 @@
1
1
  module Cartman
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
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.3.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-23 00:00:00.000000000 Z
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: 3094534765024072547
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: 3094534765024072547
91
+ hash: 1475251034207197325
92
92
  requirements: []
93
93
  rubyforge_project:
94
94
  rubygems_version: 1.8.23