awesome_o 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +114 -0
- data/Rakefile +1 -0
- data/awesome_o.gemspec +22 -0
- data/lib/awesome_o.rb +13 -0
- data/lib/awesome_o/cart.rb +170 -0
- data/lib/awesome_o/configuration.rb +18 -0
- data/lib/awesome_o/item.rb +71 -0
- data/lib/awesome_o/item_collection.rb +18 -0
- data/lib/awesome_o/version.rb +3 -0
- data/spec/cart_spec.rb +259 -0
- data/spec/item_collection_spec.rb +29 -0
- data/spec/item_spec.rb +72 -0
- data/spec/spec_helper.rb +9 -0
- metadata +92 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 30a48a2ae38fffe8accddd03c162793b902a9001
|
4
|
+
data.tar.gz: 102caa5f774e27de2f0c5d220d60f2071a677e47
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: aaf745d0d79bf6184e792919b663e8fdf575ab5c892f037994fe2f4e443e30697ec3be05da2c9f6257afe112e30dc0ee7bfa939062d741d3824cdb201fbe44f9
|
7
|
+
data.tar.gz: f8b853877ad3d3d5ddd0e7e852c7ccf9d9e16844c9cdf541085140c0810ca58a97f7ecccdecec8a0d6a521e3ffe8e27c8d29e177510d61e59684fe37f6281060
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Will Cosgrove
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# AwesomeO
|
2
|
+
[![Build Status](https://travis-ci.org/hadees/awesome_o.svg?branch=master)](https://travis-ci.org/hadees/awesome_o)
|
3
|
+
|
4
|
+
![](http://i.imgur.com/rziNSZZ.png)
|
5
|
+
|
6
|
+
AwesomeO is a framework agnostic, redis backed, cart system. It is not a POS, or a full fledged ecommerce system. Just the cart, man.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'awesome_o'
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install awesome_o
|
21
|
+
|
22
|
+
## Setup
|
23
|
+
|
24
|
+
AwesomeO has a few (read 3) configuration options you can set, most likely in an initializer file. Here's an example configuration:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
# config/initializers/awesome_o.rb
|
28
|
+
AwesomeO.config do |c|
|
29
|
+
c.cart_expires_in = 604800 # one week, in seconds. This is the default
|
30
|
+
c.unit_cost_field = :unit_cost # for cart totaling
|
31
|
+
c.quantity_field = :quantity # for quantity totaling
|
32
|
+
c.redis = Redis.new # set the redis connection here
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
- The `cart_expires_in=` setting will let you set how long a cart should live. If no items are added to the cart before the time expires, the cart will be cleared. If you want to disable cart expiration, set this to `-1`.
|
37
|
+
- `unit_cost_field=` lets you tell AwesomeO where you're storing the unit_cost of each item, so that you can use the `cost` method on the item, and the `total` method on `Cart`.
|
38
|
+
- `quantity_field=` lets you tell AwesomeO where you're storing the quantity of each item. The `Item#cost` method uses this along with the `unit_cost` field to determine the cost.
|
39
|
+
- `redis=` lets you set the redis connection you want to use. Note that this is not redis connection options, this is an actual instance of `Redis`.
|
40
|
+
|
41
|
+
## Usage
|
42
|
+
|
43
|
+
To create a new shopping cart, just call `AwesomeO::Cart.new(user.id)`. The parameter for `Cart.new()` is a unique id. If you don't want a user to have more than one cart at a time, it's generally best to set this to the user's id. Then to add an item to the cart, just call `cart.add_item(data)` which takes a hash of data that you want to store. Then to retrieve the items, you just call `cart.items` which will give you an array of all the items they've added.
|
44
|
+
|
45
|
+
The returned Items come back as `AwesomeO::Item` instances, which have a few special methods to be aware of:
|
46
|
+
|
47
|
+
- `cost` - which will return the `:unit_cost` multiplied by the `:quantity`.
|
48
|
+
- `destroy` - which will remove the item from the cart
|
49
|
+
- `cart` - which will return the parent cart, think ActiveRecord association
|
50
|
+
- `_id` - which will return the id of the item, if you need that for whatever reason
|
51
|
+
- `_key` - which will return the redis key the data is stored in. Probably won't need that, but it's there.
|
52
|
+
- `#{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`).
|
53
|
+
|
54
|
+
The `Cart` object also has some handy methods that you should be aware of:
|
55
|
+
|
56
|
+
- `add_item(data)` - which is the life blood of AwesomeO. 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:
|
57
|
+
- `:id` - (*required*) to store the ID of the model you're adding
|
58
|
+
- `:type` - (*required*) to store the class of the model you're adding.
|
59
|
+
- `:unit_cost` - This field is required if you want to take advantage of the `Item#cost` method, and the `Cart#total` method.
|
60
|
+
- `:quantity` - which if you use will let you use the `Cart#quantity` and `Cart#total` methods without any extra configuration.
|
61
|
+
- `remove_item(item)` - which, you guessed it, removes an item. This method takes an Item object, not a hash.
|
62
|
+
- `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.
|
63
|
+
- `find(Product)` - This will return the `Item` object that represents the object passed in. It works like `contains?` and uses class, and id. It only works if the `:id` and `:type` keys are set in the item's data hash.
|
64
|
+
- `items` - this returns a magic array of all the items. You can also pass in a type string, which will return a magic array of all the items with a matching type. I call it magic because you can call on it:
|
65
|
+
- `each_with_object` - which will act like a regular `each` call, but the block will yield the `Item` and the object it represents by using the `:type` and `:id` keys.
|
66
|
+
- `total` - will sum all of the `cost` return values of all of the items in the cart. For this to work, the `:unit_cost` and `:quantity` fields need to be set for all items.
|
67
|
+
- `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.
|
68
|
+
- `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`
|
69
|
+
- `ttl` - will tell you how many seconds until the cart expires. It will return -1 if the cart will never expire.
|
70
|
+
- `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`.
|
71
|
+
- `destroy!` - which will delete the cart, and all the line_items out of it.
|
72
|
+
- `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.
|
73
|
+
|
74
|
+
Lets walk through an example implementation with a Rails app that has a User model and a Product model.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
# app/models/user.rb
|
78
|
+
class User < ActiveRecord::Base
|
79
|
+
#...
|
80
|
+
def cart
|
81
|
+
AwesomeO::Cart.new(id)
|
82
|
+
end
|
83
|
+
#...
|
84
|
+
end
|
85
|
+
```
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
# app/controllers/products_controller.rb
|
89
|
+
class ProductsController < ApplicationController
|
90
|
+
#...
|
91
|
+
# /products/:id/add_to_cart
|
92
|
+
def add_to_cart
|
93
|
+
@product = Product.find(params[:id])
|
94
|
+
current_user.cart.add_item(id: @product.id, name: @product.name, unit_cost: @product.cost, cost: @product.cost * params[:quantity], quantity: params[:quantity])
|
95
|
+
end
|
96
|
+
#...
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
```haml
|
101
|
+
-# app/view/cart/show.html.haml
|
102
|
+
%h1 Cart - Total: #{@cart.total}
|
103
|
+
%ul
|
104
|
+
- @cart.items.each do |item|
|
105
|
+
%li #{item.name} - #{item.cost}
|
106
|
+
```
|
107
|
+
|
108
|
+
## Contributing
|
109
|
+
|
110
|
+
1. Fork it
|
111
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
112
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
113
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
114
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/awesome_o.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'awesome_o/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'awesome_o'
|
8
|
+
gem.version = AwesomeO::VERSION
|
9
|
+
gem.authors = ['Evan Alter']
|
10
|
+
gem.email = ['evan.alter@gmail.com']
|
11
|
+
gem.description = 'AwesomeO is a frameworke agnostic, redis-backed, shopping cart system'
|
12
|
+
gem.summary = 'Originaly forked from the gem Cartman'
|
13
|
+
gem.homepage = 'http://github.com/hadees/awesome-o'
|
14
|
+
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.require_paths = ['lib']
|
18
|
+
|
19
|
+
gem.add_dependency('redis')
|
20
|
+
|
21
|
+
gem.add_development_dependency('rspec', '~> 3.0.0')
|
22
|
+
end
|
data/lib/awesome_o.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'awesome_o/version'
|
2
|
+
require 'awesome_o/configuration'
|
3
|
+
require 'awesome_o/cart'
|
4
|
+
require 'awesome_o/item'
|
5
|
+
require 'awesome_o/item_collection'
|
6
|
+
|
7
|
+
module AwesomeO
|
8
|
+
def self.config
|
9
|
+
@config ||= Configuration.new
|
10
|
+
yield @config if block_given?
|
11
|
+
@config
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
module AwesomeO
|
2
|
+
class Cart
|
3
|
+
CART_LINE_ITEM_ID_KEY = 'awesome_o:line_item:id'.freeze
|
4
|
+
|
5
|
+
def initialize(uid)
|
6
|
+
@uid = uid
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_item(options)
|
10
|
+
raise 'Must specify both :id and :type' unless options.key?(:id) && options.key?(:type)
|
11
|
+
line_item_id = redis.incr CART_LINE_ITEM_ID_KEY
|
12
|
+
redis.pipelined do
|
13
|
+
redis.mapped_hmset("awesome_o:line_item:#{line_item_id}", options)
|
14
|
+
redis.hincrby("awesome_o:line_item:#{line_item_id}", :_version, 1)
|
15
|
+
redis.sadd key, line_item_id
|
16
|
+
redis.sadd index_key, "#{options[:type]}:#{options[:id]}"
|
17
|
+
redis.set index_key_for(options), line_item_id
|
18
|
+
end
|
19
|
+
touch
|
20
|
+
get_item(line_item_id)
|
21
|
+
end
|
22
|
+
|
23
|
+
def remove_item(item)
|
24
|
+
redis.del "awesome_o:line_item:#{item._id}"
|
25
|
+
redis.srem key, item._id
|
26
|
+
redis.srem index_key, "#{item.type}:#{item.id}"
|
27
|
+
redis.del index_key_for(item)
|
28
|
+
touch
|
29
|
+
end
|
30
|
+
|
31
|
+
def items(type = nil)
|
32
|
+
if type
|
33
|
+
items = line_item_ids.collect { |item_id| get_item(item_id) }.select { |item| item.type == type }
|
34
|
+
return ItemCollection.new(items)
|
35
|
+
else
|
36
|
+
return ItemCollection.new(line_item_ids.collect { |item_id| get_item(item_id) })
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def contains?(object)
|
41
|
+
redis.sismember index_key, "#{object.class}:#{object.id}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def find(object)
|
45
|
+
get_item(redis.get(index_key_for(object)).to_i) if contains?(object)
|
46
|
+
end
|
47
|
+
|
48
|
+
def count
|
49
|
+
redis.scard key
|
50
|
+
end
|
51
|
+
|
52
|
+
def quantity
|
53
|
+
line_item_keys.collect do |item_key|
|
54
|
+
redis.hget item_key, AwesomeO.config.quantity_field
|
55
|
+
end.inject(0) { |sum, quantity| sum += quantity.to_i }
|
56
|
+
end
|
57
|
+
|
58
|
+
def total
|
59
|
+
items.collect do |item|
|
60
|
+
(item.cost * 100).to_i
|
61
|
+
end.inject(0) { |sum, cost| sum += cost } / 100.0
|
62
|
+
end
|
63
|
+
|
64
|
+
def ttl
|
65
|
+
redis.ttl key
|
66
|
+
end
|
67
|
+
|
68
|
+
def destroy!
|
69
|
+
keys = line_item_keys
|
70
|
+
keys << key
|
71
|
+
keys << index_key
|
72
|
+
keys << index_keys
|
73
|
+
keys.flatten!
|
74
|
+
redis.pipelined do
|
75
|
+
keys.each do |key|
|
76
|
+
redis.del key
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def touch
|
82
|
+
keys_to_expire = line_item_keys
|
83
|
+
keys_to_expire << key
|
84
|
+
if redis.exists index_key
|
85
|
+
keys_to_expire << index_key
|
86
|
+
keys_to_expire << index_keys
|
87
|
+
keys_to_expire.flatten!
|
88
|
+
end
|
89
|
+
redis.pipelined do
|
90
|
+
keys_to_expire.each do |item|
|
91
|
+
redis.expire item, AwesomeO.config.cart_expires_in
|
92
|
+
end
|
93
|
+
end
|
94
|
+
redis.hincrby self.class.versions_key, version_key, 1
|
95
|
+
end
|
96
|
+
|
97
|
+
def version
|
98
|
+
redis.hget(self.class.versions_key, version_key).to_i
|
99
|
+
end
|
100
|
+
|
101
|
+
def reassign(new_id)
|
102
|
+
if redis.exists key
|
103
|
+
new_index_keys = items.collect do |item|
|
104
|
+
index_key_for(item, new_id)
|
105
|
+
end
|
106
|
+
redis.rename key, key(new_id)
|
107
|
+
redis.rename index_key, index_key(new_id)
|
108
|
+
index_keys.zip(new_index_keys).each do |key, value|
|
109
|
+
redis.rename key, value
|
110
|
+
end
|
111
|
+
end
|
112
|
+
@uid = new_id
|
113
|
+
end
|
114
|
+
|
115
|
+
def cache_key
|
116
|
+
"cart/#{@uid}-#{version}"
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def key(id = @uid)
|
122
|
+
"awesome_o:cart:#{id}"
|
123
|
+
end
|
124
|
+
|
125
|
+
def index_key(id = @uid)
|
126
|
+
key(id) + ':index'
|
127
|
+
end
|
128
|
+
|
129
|
+
def version_key(id = @uid)
|
130
|
+
id
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.versions_key
|
134
|
+
'awesome_o:cart:versions'
|
135
|
+
end
|
136
|
+
|
137
|
+
def index_keys(id = @uid)
|
138
|
+
redis.keys "#{index_key(id)}:*"
|
139
|
+
end
|
140
|
+
|
141
|
+
def index_key_for(object, id = @uid)
|
142
|
+
case object
|
143
|
+
when Hash
|
144
|
+
index_key(id) + ":#{object[:type]}:#{object[:id]}"
|
145
|
+
when Item
|
146
|
+
index_key(id) + ":#{object.type}:#{object.id}"
|
147
|
+
else
|
148
|
+
index_key(id) + ":#{object.class}:#{object.id}"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def line_item_ids
|
153
|
+
redis.smembers key
|
154
|
+
end
|
155
|
+
|
156
|
+
def line_item_keys
|
157
|
+
line_item_ids.collect { |id| "awesome_o:line_item:#{id}" }
|
158
|
+
end
|
159
|
+
|
160
|
+
def get_item(id)
|
161
|
+
Item.new(id, @uid, redis.hgetall("awesome_o:line_item:#{id}").each_with_object({}) { |(k, v), hash| hash[k.to_sym] = v; hash })
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
def redis
|
167
|
+
AwesomeO.config.redis
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'redis'
|
2
|
+
|
3
|
+
module AwesomeO
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :cart_expires_in, :unit_cost_field, :quantity_field
|
6
|
+
attr_writer :redis
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@cart_expires_in = 604_800
|
10
|
+
@unit_cost_field = :unit_cost
|
11
|
+
@quantity_field = :quantity
|
12
|
+
end
|
13
|
+
|
14
|
+
def redis
|
15
|
+
@redis ||= Redis.new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module AwesomeO
|
2
|
+
class Item
|
3
|
+
def initialize(item_id, cart_id, data)
|
4
|
+
@id = item_id
|
5
|
+
@cart_id = cart_id
|
6
|
+
@data = data
|
7
|
+
end
|
8
|
+
|
9
|
+
def _id
|
10
|
+
@id
|
11
|
+
end
|
12
|
+
|
13
|
+
def cost
|
14
|
+
unit_cost = (@data.fetch(AwesomeO.config.unit_cost_field).to_f * 100).to_i
|
15
|
+
quantity = @data.fetch(AwesomeO.config.quantity_field).to_i
|
16
|
+
(unit_cost * quantity) / 100.0
|
17
|
+
end
|
18
|
+
|
19
|
+
def cart
|
20
|
+
@cart ||= Cart.new(@cart_id)
|
21
|
+
end
|
22
|
+
|
23
|
+
def destroy
|
24
|
+
cart.remove_item(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(item)
|
28
|
+
@id == item._id
|
29
|
+
end
|
30
|
+
|
31
|
+
def touch
|
32
|
+
cart.touch
|
33
|
+
redis.hincrby _key, :_version, 1
|
34
|
+
end
|
35
|
+
|
36
|
+
def _key
|
37
|
+
"awesome_o:line_item:#{@id}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def _version
|
41
|
+
super.to_i
|
42
|
+
end
|
43
|
+
|
44
|
+
def cache_key
|
45
|
+
"item/#{@id}-#{_version}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def method_missing(method, *args, &block)
|
49
|
+
if method.to_s.end_with?('=')
|
50
|
+
redis.hset _key, method[0..-2], args[0].to_s
|
51
|
+
@data.store(method[0..-2].to_sym, args[0].to_s)
|
52
|
+
version = touch
|
53
|
+
@data.store(:_version, version)
|
54
|
+
elsif @data.keys.include?(method)
|
55
|
+
@data.fetch(method)
|
56
|
+
else
|
57
|
+
super
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def respond_to_missing?(method, include_private = false)
|
62
|
+
method.to_s.end_with?('=') || @data.keys.include?(method) || super
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def redis
|
68
|
+
AwesomeO.config.redis
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module AwesomeO
|
4
|
+
class ItemCollection < DelegateClass(Array)
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(array)
|
8
|
+
@results = array
|
9
|
+
super(@results)
|
10
|
+
end
|
11
|
+
|
12
|
+
def each_with_object
|
13
|
+
@results.each do |result|
|
14
|
+
yield result, eval(result.type).send(:find, result.id)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/spec/cart_spec.rb
ADDED
@@ -0,0 +1,259 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AwesomeO do
|
4
|
+
describe AwesomeO::Cart do
|
5
|
+
let(:Bottle) { Struct.new(:id) }
|
6
|
+
let(:cart) { AwesomeO::Cart.new(1) }
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
AwesomeO.config.redis.flushdb
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#key' do
|
13
|
+
it 'should return a proper key string' do
|
14
|
+
expect(cart.send(:key)).to eq('awesome_o:cart:1')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#add_item' do
|
19
|
+
before(:each) do
|
20
|
+
cart.add_item(id: 17, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, quantity: 2)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'creates a line item key' do
|
24
|
+
expect(AwesomeO.config.redis.exists('awesome_o:line_item:1')).to be true
|
25
|
+
end
|
26
|
+
|
27
|
+
it "adds that line item key's id to the cart set" do
|
28
|
+
expect(AwesomeO.config.redis.sismember(cart.send(:key), 1)).to be true
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should expire the line_item_keys in the amount of time specified' do
|
32
|
+
expect(cart.ttl).to eq(AwesomeO.config.cart_expires_in)
|
33
|
+
expect(AwesomeO.config.redis.ttl('awesome_o:line_item:1')).to eq(AwesomeO.config.cart_expires_in)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should add an index key to be able to look up by type and ID' do
|
37
|
+
expect(AwesomeO.config.redis.exists('awesome_o:cart:1:index')).to be true
|
38
|
+
expect(AwesomeO.config.redis.sismember('awesome_o:cart:1:index', 'Bottle:17')).to be true
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should squack if type and/or ID are not set' do
|
42
|
+
expect { cart.add_item(id: 18, name: 'Cordeux', unit_cost: 92.12, quantity: 2) }.to raise_error('Must specify both :id and :type')
|
43
|
+
expect { cart.add_item(type: 'Bottle', name: 'Cordeux', unit_cost: 92.12, quantity: 2) }.to raise_error('Must specify both :id and :type')
|
44
|
+
expect { cart.add_item(name: 'Cordeux', unit_cost: 92.12, quantity: 2) }.to raise_error('Must specify both :id and :type')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should return an Item' do
|
48
|
+
item = cart.add_item(id: 34, type: 'Bottle', name: 'Cabernet', unit_cost: 92.12, quantity: 2)
|
49
|
+
expect(item.class).to eq(AwesomeO::Item)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#remove_item' do
|
54
|
+
it 'should remove the id from the set, and delete the line_item key' do
|
55
|
+
item = cart.add_item(id: 17, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, quantity: 2)
|
56
|
+
item_id = item._id
|
57
|
+
cart.remove_item(item)
|
58
|
+
expect(AwesomeO.config.redis.sismember(cart.send(:key), item_id)).to be false
|
59
|
+
expect(AwesomeO.config.redis.exists("awesome_o:line_item:#{item_id}")).to be false
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should not delete the indecies for other items' do
|
63
|
+
item = cart.add_item(id: 17, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, quantity: 2)
|
64
|
+
item2 = cart.add_item(id: 18, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, quantity: 2)
|
65
|
+
expect(AwesomeO.config.redis.exists('awesome_o:cart:1:index:Bottle:17')).to be true
|
66
|
+
expect(AwesomeO.config.redis.exists('awesome_o:cart:1:index:Bottle:18')).to be true
|
67
|
+
cart.remove_item(item)
|
68
|
+
expect(AwesomeO.config.redis.exists('awesome_o:cart:1:index:Bottle:17')).to be false
|
69
|
+
expect(AwesomeO.config.redis.exists('awesome_o:cart:1:index:Bottle:18')).to be true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '#items' do
|
74
|
+
before(:each) do
|
75
|
+
cart.add_item(id: 17, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, quantity: 2)
|
76
|
+
cart.add_item(id: 34, type: 'Bottle', name: 'Cabernet', unit_cost: 92.12, quantity: 2)
|
77
|
+
cart.add_item(id: 35, type: 'GiftCard', name: 'Gift Card', unit_cost: 100.00, quantity: 1)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should return an ItemCollection of Items' do
|
81
|
+
expect(cart.items.class).to be(AwesomeO::ItemCollection)
|
82
|
+
expect(cart.items.first.class).to be(AwesomeO::Item)
|
83
|
+
expect(cart.items.first.id).to eq('17')
|
84
|
+
expect(cart.items.first.name).to eq('Bordeux')
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should return all items in cart if no filter is given' do
|
88
|
+
expect(cart.items.size).to eq(3)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should return a subset of the items if a filter is given' do
|
92
|
+
expect(cart.items('Bottle').size).to eq(2)
|
93
|
+
expect(cart.items('GiftCard').size).to eq(1)
|
94
|
+
expect(cart.items('SomethingElse').size).to eq(0)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#contains?(item)' do
|
99
|
+
before(:all) do
|
100
|
+
Bottle = Struct.new(:id)
|
101
|
+
end
|
102
|
+
|
103
|
+
before(:each) do
|
104
|
+
cart.add_item(id: 17, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, quantity: 2)
|
105
|
+
cart.add_item(id: 34, type: 'Bottle', name: 'Cabernet', unit_cost: 92.12, quantity: 2)
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'should be able to tell you that an item in the cart is present' do
|
109
|
+
expect(cart.contains?(Bottle.new(17))).to be true
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should be able to tell you that an item in the cart is absent' do
|
113
|
+
expect(cart.contains?(Bottle.new(20))).to be false
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should be able to tell you that an item in the cart is absent if it's been removed" do
|
117
|
+
cart.remove_item(cart.items.first)
|
118
|
+
expect(cart.contains?(Bottle.new(17))).to be false
|
119
|
+
cart.remove_item(cart.items.last)
|
120
|
+
expect(cart.contains?(Bottle.new(34))).to be false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe '#find(item)' do
|
125
|
+
before(:each) do
|
126
|
+
cart.add_item(id: 17, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, quantity: 2)
|
127
|
+
cart.add_item(id: 34, type: 'Bottle', name: 'Cabernet', unit_cost: 92.12, quantity: 2)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'should take some object, and return the Item that corresponds to it' do
|
131
|
+
expect(cart.find(Bottle.new(17)).quantity).to eq('2')
|
132
|
+
expect(cart.find(Bottle.new(17)).name).to eq('Bordeux')
|
133
|
+
expect(cart.find(Bottle.new(34)).name).to eq('Cabernet')
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'should return nil if the Item is not in the cart' do
|
137
|
+
expect(cart.find(Bottle.new(23))).to be(nil)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe '#count' do
|
142
|
+
it 'should return the number of items in the cart' do
|
143
|
+
cart.add_item(id: 17, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, quantity: 2)
|
144
|
+
cart.add_item(id: 34, type: 'Bottle', name: 'Cabernet', unit_cost: 92.12, quantity: 2)
|
145
|
+
expect(cart.count).to eq(2)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe '#quantity' do
|
150
|
+
it 'should return the sum of the default quantity field' do
|
151
|
+
cart.add_item(id: 17, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, quantity: 2)
|
152
|
+
cart.add_item(id: 34, type: 'Bottle', name: 'Cabernet', unit_cost: 92.12, quantity: 2)
|
153
|
+
expect(cart.quantity).to eq(4)
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'should return the sum of the defined quantity field' do
|
157
|
+
AwesomeO.config do |c|
|
158
|
+
c.quantity_field = :qty
|
159
|
+
end
|
160
|
+
cart.add_item(id: 17, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, qty: 2)
|
161
|
+
cart.add_item(id: 34, type: 'Bottle', name: 'Cabernet', unit_cost: 92.12, qty: 2)
|
162
|
+
expect(cart.quantity).to eq(4)
|
163
|
+
AwesomeO.config do |c|
|
164
|
+
c.quantity_field = :quantity
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe '#total' do
|
170
|
+
it 'should return 0 when no items are in the cart' do
|
171
|
+
expect(cart.total).to eq(0)
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'should total the default costs field' do
|
175
|
+
cart.add_item(id: 17, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, quantity: 2)
|
176
|
+
cart.add_item(id: 34, type: 'Bottle', name: 'Cabernet', unit_cost: 92.12, quantity: 2)
|
177
|
+
expect(cart.total).to eq(368.48)
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'should total whatever cost field the user sets' do
|
181
|
+
AwesomeO.config do |c|
|
182
|
+
c.unit_cost_field = :unit_cost_in_cents
|
183
|
+
end
|
184
|
+
cart.add_item(id: 17, type: 'Bottle', name: 'Bordeux', unit_cost_in_cents: 9212, quantity: 2)
|
185
|
+
cart.add_item(id: 34, type: 'Bottle', name: 'Cabernet', unit_cost_in_cents: 9212, quantity: 2)
|
186
|
+
expect(cart.total).to eq(36_848)
|
187
|
+
AwesomeO.config do |c|
|
188
|
+
c.unit_cost_field = :unit_cost
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
describe '#destroy' do
|
194
|
+
it 'should delete the line_item keys, the index key, and the cart key' do
|
195
|
+
cart.add_item(id: 17, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, cost_in_cents: 18_424, quantity: 2)
|
196
|
+
cart.add_item(id: 34, type: 'Bottle', name: 'Cabernet', unit_cost: 92.12, cost_in_cents: 18_424, quantity: 2)
|
197
|
+
cart.destroy!
|
198
|
+
expect(AwesomeO.config.redis.exists('awesome_o:cart:1')).to be false
|
199
|
+
expect(AwesomeO.config.redis.exists('awesome_o:line_item:1')).to be false
|
200
|
+
expect(AwesomeO.config.redis.exists('awesome_o:line_item:2')).to be false
|
201
|
+
expect(AwesomeO.config.redis.exists('awesome_o:cart:1:index')).to be false
|
202
|
+
expect(AwesomeO.config.redis.exists('awesome_o:cart:1:index:Bottle:17')).to be false
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
describe '#touch' do
|
207
|
+
it 'should reset the TTL' do
|
208
|
+
cart.add_item(id: 17, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, cost_in_cents: 18_424, quantity: 2)
|
209
|
+
cart.touch
|
210
|
+
expect(cart.ttl).to eq(AwesomeO.config.cart_expires_in)
|
211
|
+
expect(AwesomeO.config.redis.ttl('awesome_o:cart:1:index')).to eq(AwesomeO.config.cart_expires_in)
|
212
|
+
expect(AwesomeO.config.redis.ttl('awesome_o:cart:1:index:Bottle:17')).to eq(AwesomeO.config.cart_expires_in)
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'should record that the cart was updated' do
|
216
|
+
cart.add_item(id: 17, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, cost_in_cents: 18_424, quantity: 2)
|
217
|
+
cart.touch
|
218
|
+
expect(cart.version).to eq(2)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
describe '#reassign' do
|
223
|
+
it 'should only change the @uid if no key exists' do
|
224
|
+
cart.reassign(2)
|
225
|
+
expect(cart.send(:key)[-1]).to eq('2')
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'should rename the key, and index_key if it exists' do
|
229
|
+
cart.add_item(id: 17, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, cost_in_cents: 18_424, quantity: 1)
|
230
|
+
cart.add_item(id: 18, type: 'Bottle', name: 'Merlot', unit_cost: 92.12, cost_in_cents: 18_424, quantity: 3)
|
231
|
+
expect(cart.quantity).to be(4)
|
232
|
+
cart.reassign(2)
|
233
|
+
expect(cart.items.size).to be(2)
|
234
|
+
expect(AwesomeO::Cart.new(2).quantity).to be(4)
|
235
|
+
expect(AwesomeO.config.redis.exists('awesome_o:cart:1')).to be false
|
236
|
+
expect(AwesomeO.config.redis.exists('awesome_o:cart:1:index')).to be false
|
237
|
+
expect(AwesomeO.config.redis.exists('awesome_o:cart:1:index:Bottle:17')).to be false
|
238
|
+
expect(AwesomeO.config.redis.exists('awesome_o:cart:2')).to be true
|
239
|
+
expect(AwesomeO.config.redis.exists('awesome_o:cart:2:index')).to be true
|
240
|
+
expect(AwesomeO.config.redis.exists('awesome_o:cart:2:index:Bottle:17')).to be true
|
241
|
+
expect(cart.send(:key)[-1]).to eq('2')
|
242
|
+
cart.add_item(id: 19, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, cost_in_cents: 18_424, quantity: 2)
|
243
|
+
cart.reassign(1)
|
244
|
+
expect(cart.items.size).to be(3)
|
245
|
+
expect(AwesomeO.config.redis.exists('awesome_o:cart:2')).to be false
|
246
|
+
expect(AwesomeO.config.redis.exists('awesome_o:cart:2:index')).to be false
|
247
|
+
expect(AwesomeO.config.redis.exists('awesome_o:cart:1')).to be true
|
248
|
+
expect(AwesomeO.config.redis.exists('awesome_o:cart:1:index')).to be true
|
249
|
+
expect(cart.send(:key)[-1]).to eq('1')
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
describe '#cache_key' do
|
254
|
+
it 'should return /cart/{cart_id}-{version}/' do
|
255
|
+
expect(cart.cache_key).to eq("cart/#{cart.instance_variable_get(:@uid)}-#{cart.version}")
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AwesomeO do
|
4
|
+
describe AwesomeO::ItemCollection do
|
5
|
+
let(:cart) { AwesomeO::Cart.new(1) }
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
AwesomeO.config.redis.flushdb
|
9
|
+
Object.send(:remove_const, :Bottle) if defined?(Bottle)
|
10
|
+
Bottle = double
|
11
|
+
allow(Bottle).to receive(:find).and_return(bottle)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#each_with_object' do
|
15
|
+
let(:bottle) { double('Bottle') }
|
16
|
+
|
17
|
+
it 'should be magical' do
|
18
|
+
cart.add_item(id: 17, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, cost: 184.24, quantity: 2)
|
19
|
+
expect { |b| cart.items.each_with_object(&b) }.to yield_successive_args([cart.items.first, bottle])
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should work with items('Type')" do
|
23
|
+
cart.add_item(id: 17, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, cost: 184.24, quantity: 2)
|
24
|
+
cart.add_item(id: 27, type: 'UserGiftCard', unit_cost: 25, quantity: 1)
|
25
|
+
expect { cart.items('Bottle').each_with_object {} }.not_to raise_error
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/spec/item_spec.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AwesomeO do
|
4
|
+
describe AwesomeO::Item do
|
5
|
+
let(:cart) { AwesomeO::Cart.new(1) }
|
6
|
+
let(:item) { cart.add_item(id: 17, type: 'Bottle', name: 'Bordeux', unit_cost: 92.12, quantity: 2) }
|
7
|
+
|
8
|
+
describe 'data getters and setters' do
|
9
|
+
it 'should let me get data stored for the item' do
|
10
|
+
expect(item.id).to eq('17')
|
11
|
+
expect(item.type).to eq('Bottle')
|
12
|
+
expect(item.cost).to eq(184.24)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should let me modify data stored for the item' do
|
16
|
+
expect(item.quantity).to eq('2')
|
17
|
+
item.quantity = 3
|
18
|
+
expect(item.quantity).to eq('3')
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should immediately save data back to redis' do
|
22
|
+
expect(item.quantity).to eq('2')
|
23
|
+
item.quantity = 3
|
24
|
+
new_item = cart.send(:get_item, item._id)
|
25
|
+
expect(new_item.quantity).to eq('3')
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should touch the item and cart' do
|
29
|
+
expect { item.quantity = 4 }.to change { item._version }.by(1)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should raise NoMethodError if you use a getter that there isn't a key for" do
|
33
|
+
expect { item.weight }.to raise_error(NoMethodError)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should respond_to the getters and setters' do
|
37
|
+
expect(item.respond_to?(:name)).to be true
|
38
|
+
expect(item.respond_to?(:name=)).to be true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#cost' do
|
43
|
+
it 'should be equal to the unit_cost multiplied by the quantity' do
|
44
|
+
expect(item.cost).to eq(184.24)
|
45
|
+
item.quantity = 3
|
46
|
+
expect(item.cost).to eq(276.36)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#destroy' do
|
51
|
+
it 'should remove the item from the cart' do
|
52
|
+
item_id = item._id
|
53
|
+
item.destroy
|
54
|
+
expect(AwesomeO.config.redis.sismember(cart.send(:key), item_id)).to be false
|
55
|
+
expect(AwesomeO.config.redis.exists("awesome_o:line_item:#{item_id}")).to be false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#touch' do
|
60
|
+
it 'should record that the record was changed' do
|
61
|
+
item.touch
|
62
|
+
expect(item._version).to eq(1)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#cache_key' do
|
67
|
+
it 'should return item/{id}-{version}' do
|
68
|
+
expect(item.cache_key).to eq("item/#{item._id}-#{item._version}")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: awesome_o
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Evan Alter
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-10-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: redis
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.0.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.0.0
|
41
|
+
description: AwesomeO is a frameworke agnostic, redis-backed, shopping cart system
|
42
|
+
email:
|
43
|
+
- evan.alter@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- ".travis.yml"
|
50
|
+
- Gemfile
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
- Rakefile
|
54
|
+
- awesome_o.gemspec
|
55
|
+
- lib/awesome_o.rb
|
56
|
+
- lib/awesome_o/cart.rb
|
57
|
+
- lib/awesome_o/configuration.rb
|
58
|
+
- lib/awesome_o/item.rb
|
59
|
+
- lib/awesome_o/item_collection.rb
|
60
|
+
- lib/awesome_o/version.rb
|
61
|
+
- spec/cart_spec.rb
|
62
|
+
- spec/item_collection_spec.rb
|
63
|
+
- spec/item_spec.rb
|
64
|
+
- spec/spec_helper.rb
|
65
|
+
homepage: http://github.com/hadees/awesome-o
|
66
|
+
licenses: []
|
67
|
+
metadata: {}
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 2.5.1
|
85
|
+
signing_key:
|
86
|
+
specification_version: 4
|
87
|
+
summary: Originaly forked from the gem Cartman
|
88
|
+
test_files:
|
89
|
+
- spec/cart_spec.rb
|
90
|
+
- spec/item_collection_spec.rb
|
91
|
+
- spec/item_spec.rb
|
92
|
+
- spec/spec_helper.rb
|