cart 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.textile +88 -0
- data/Rakefile +53 -0
- data/TODO +7 -0
- data/cart.gemspec +21 -0
- data/lib/cart/advanced.rb +108 -0
- data/lib/cart/config.rb +30 -0
- data/lib/cart/logger_stub.rb +15 -0
- data/lib/cart/simple.rb +78 -0
- data/spec/cart/advanced_spec.rb +135 -0
- data/spec/cart/config_spec.rb +55 -0
- data/spec/cart/logger_stub_spec.rb +18 -0
- data/spec/cart/simple_spec.rb +80 -0
- data/spec/factories.rb +22 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +13 -0
- metadata +69 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Jakub Stastny aka Botanicus
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
h1. About
|
2
|
+
|
3
|
+
Cart is framework agnostic solution for shopping cart. There are two existing imlementations. First is *Cart::Simple* which is just basic cart which can store only ID of products without any metadata. The second is *Cart::Advanced* and it is good when you have not just product, but also some metadata as size or color of product etc.
|
4
|
+
|
5
|
+
h1. Initialization
|
6
|
+
|
7
|
+
h2. Cart::Simple
|
8
|
+
|
9
|
+
<pre>
|
10
|
+
require "cart/simple"
|
11
|
+
Cart.product_model = Product
|
12
|
+
</pre>
|
13
|
+
|
14
|
+
h2. Cart::Advanced
|
15
|
+
|
16
|
+
<pre>
|
17
|
+
require "cart/advanced"
|
18
|
+
Cart.metadata_model = OrderItem
|
19
|
+
</pre>
|
20
|
+
|
21
|
+
Metadata model must respond to serialize object method and deserialize class method for storing and restoring data:
|
22
|
+
|
23
|
+
<pre>
|
24
|
+
class OrderItem
|
25
|
+
include DataMapper::Resource
|
26
|
+
class << self
|
27
|
+
def deserialize(data)
|
28
|
+
self.new(YAML::load(data))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def serialize
|
33
|
+
{:size => self.size, :product_id => self.product.id}.to_yaml
|
34
|
+
end
|
35
|
+
end
|
36
|
+
</pre>
|
37
|
+
|
38
|
+
h2. Logger
|
39
|
+
|
40
|
+
Cart is just simple API for your favorite framework, so it haven't any logger itself and it just use the logger of your framework. The only thing you need is setup it:
|
41
|
+
|
42
|
+
<pre>
|
43
|
+
# whatever what respond to debug method
|
44
|
+
Cart.logger = Merb.logger
|
45
|
+
</pre>
|
46
|
+
|
47
|
+
Maybe you would like to hook the logger, for example add information that the message comes from cart or maybe you just use framework which hasn't any logger at all. In this case you can set logger to lambda or whatever callable object:
|
48
|
+
|
49
|
+
<pre>
|
50
|
+
# or whatever what respond to to_proc method
|
51
|
+
Cart.logger = method(:puts)
|
52
|
+
Cart.logger = lambda { |msg| puts(msg) }
|
53
|
+
</pre>
|
54
|
+
|
55
|
+
h1. Usage
|
56
|
+
|
57
|
+
<pre>
|
58
|
+
class Application < Merb::Controller
|
59
|
+
# it must be deserialized in each request,
|
60
|
+
# even with memory session store
|
61
|
+
before :initialize_cart
|
62
|
+
|
63
|
+
def initialize_cart
|
64
|
+
@cart = cookies[:cart] ? Cart.load(cookies[:cart]) : Cart.new
|
65
|
+
end
|
66
|
+
|
67
|
+
def save_cart
|
68
|
+
cookies[:cart] = @cart.save
|
69
|
+
end
|
70
|
+
|
71
|
+
def reset_cart
|
72
|
+
cookies.delete(:cart)
|
73
|
+
@cart = Cart.new
|
74
|
+
end
|
75
|
+
|
76
|
+
def add_to_cart(order_item)
|
77
|
+
self.initialize_cart unless @cart
|
78
|
+
@cart.add(order_item)
|
79
|
+
self.save_cart
|
80
|
+
end
|
81
|
+
|
82
|
+
# Remove all the items with responding product and inverted values from cart
|
83
|
+
def remove_from_cart(order_item)
|
84
|
+
@cart.remove(order_item)
|
85
|
+
self.save_cart
|
86
|
+
end
|
87
|
+
end
|
88
|
+
</pre>
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
|
4
|
+
GEM_NAME = "cart"
|
5
|
+
AUTHOR = "Jakub Stastny aka Botanicus"
|
6
|
+
EMAIL = "knava.bestvinensis@gmail.com"
|
7
|
+
HOMEPAGE = "http://101Ideas.cz/"
|
8
|
+
SUMMARY = "Framework agnostic cart solution"
|
9
|
+
GEM_VERSION = "0.0.1"
|
10
|
+
|
11
|
+
spec = Gem::Specification.new do |s|
|
12
|
+
s.name = GEM_NAME
|
13
|
+
s.version = GEM_VERSION
|
14
|
+
s.platform = Gem::Platform::RUBY
|
15
|
+
s.has_rdoc = true
|
16
|
+
s.extra_rdoc_files = ["README.textile", "LICENSE"]
|
17
|
+
s.summary = SUMMARY
|
18
|
+
s.description = s.summary
|
19
|
+
s.author = AUTHOR
|
20
|
+
s.email = EMAIL
|
21
|
+
s.homepage = HOMEPAGE
|
22
|
+
s.require_path = 'lib'
|
23
|
+
s.files = %w(LICENSE README.textile Rakefile) + Dir.glob("{lib,spec,app,public,stubs}/**/*")
|
24
|
+
end
|
25
|
+
|
26
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
27
|
+
pkg.gem_spec = spec
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "Create a gemspec file"
|
31
|
+
task :gemspec do
|
32
|
+
File.open("#{GEM_NAME}.gemspec", "w") do |file|
|
33
|
+
file.puts spec.to_ruby
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def sudo_gem(cmd)
|
38
|
+
sh "#{SUDO} #{RUBY} -S gem #{cmd}", :verbose => false
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "Install #{GEM_NAME} #{GEM_VERSION}"
|
42
|
+
task :install => [ :package ] do
|
43
|
+
sudo_gem "install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources"
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "Uninstall #{GEM_NAME} #{GEM_VERSION}"
|
47
|
+
task :uninstall => [ :clobber ] do
|
48
|
+
sudo_gem "uninstall #{GEM_NAME} -v#{GEM_VERSION} -Ix"
|
49
|
+
end
|
50
|
+
|
51
|
+
require 'spec/rake/spectask'
|
52
|
+
desc 'Default: run spec examples'
|
53
|
+
task :default => 'spec'
|
data/TODO
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
- spec advanced and simple carts (other stuff should work)
|
2
|
+
- simple and advanced carts should have same interface
|
3
|
+
- it should works without logger
|
4
|
+
- move all the stuff into model (Cart::Simple.new etc)
|
5
|
+
- config should take strings as well (Cart.product_model = "Product")
|
6
|
+
- config should have default values (Product, OrderItem)
|
7
|
+
- standalone configuration class (maybe something like configatron?) => easier testing
|
data/cart.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
begin
|
2
|
+
require "rubygems/specification"
|
3
|
+
rescue SecurityError
|
4
|
+
# http://gems.github.com
|
5
|
+
end
|
6
|
+
|
7
|
+
VERSION = "0.0.1"
|
8
|
+
SPECIFICATION = ::Gem::Specification.new do |s|
|
9
|
+
s.name = "cart"
|
10
|
+
s.version = VERSION
|
11
|
+
s.authors = ["Jakub Šťastný aka Botanicus"]
|
12
|
+
s.homepage = "http://github.com/botanicus/cart"
|
13
|
+
s.summary = "Cart is framework agnostic solution for shopping cart."
|
14
|
+
s.description = "Cart is framework agnostic solution for shopping cart. There are two existing imlementations. First is *Cart::Simple* which is just basic cart which can store only ID of products without any metadata. The second is *Cart::Advanced* and it is good when you have not just product, but also some metadata as size or color of product etc."
|
15
|
+
s.cert_chain = nil
|
16
|
+
s.email = ["knava.bestvinensis", "gmail.com"].join("@")
|
17
|
+
s.files = Dir.glob("**/*") - Dir.glob("pkg/*")
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
# s.required_ruby_version = ::Gem::Requirement.new(">= 1.9.1")
|
20
|
+
# s.rubyforge_project = "cart"
|
21
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require "cart/config"
|
2
|
+
|
3
|
+
class Cart
|
4
|
+
def self.load(data)
|
5
|
+
# return new cart if nil or empty string is given
|
6
|
+
return self.new if data.nil? or data.empty?
|
7
|
+
data = data.split(";")
|
8
|
+
# compact output array, self.metadata_model.deserialize could return nil
|
9
|
+
items = data.map { |item| self.metadata_model.deserialize(item) }.compact
|
10
|
+
return self.new(*items)
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :items
|
14
|
+
|
15
|
+
def initialize(*items)
|
16
|
+
# raise ArgumentError unless items.all? { |item| item.is_a?(@config.metadata_model) || item.nil? }
|
17
|
+
@items = items
|
18
|
+
@config = self.class
|
19
|
+
@config.logger.debug("Cart initialized: #{self.inspect}")
|
20
|
+
end
|
21
|
+
|
22
|
+
# takes array of products
|
23
|
+
def items=(items)
|
24
|
+
raise ArgumentError if not items.respond_to?(:each) # not just arrays
|
25
|
+
raise ArgumentError if not items.all? { |product| product.kind_of?(@config.metadata_model) }
|
26
|
+
@items = items
|
27
|
+
end
|
28
|
+
|
29
|
+
# params_list must be list of hashes or list of @config.metadata_model instances
|
30
|
+
def add(*params_list)
|
31
|
+
params_list.each do |params|
|
32
|
+
if params.is_a?(Hash)
|
33
|
+
new_item = @config.metadata_model.new(params)
|
34
|
+
else
|
35
|
+
new_item = params
|
36
|
+
end
|
37
|
+
@items.map do |item|
|
38
|
+
if items_equal?(item, new_item)
|
39
|
+
item.count += new_item.count
|
40
|
+
@config.logger.debug("Item #{item.inspect} count was increased to #{item.count} (by #{new_item.count})")
|
41
|
+
return # important
|
42
|
+
end
|
43
|
+
end
|
44
|
+
@items.push(new_item)
|
45
|
+
@config.logger.debug("Item #{new_item.inspect} was added to cart")
|
46
|
+
return new_item.product
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def items_equal?(old, new)
|
51
|
+
# this is important, we must compare all the properties exclude count
|
52
|
+
old = old.dup.tap { |old| old.count = 1 }
|
53
|
+
new = new.dup.tap { |new| new.count = 1 }
|
54
|
+
# from form empty values goes as "", but deserialized empty items are nil
|
55
|
+
properties = new.class.properties.map(&:name)
|
56
|
+
properties.each do |property|
|
57
|
+
value = new.send(property)
|
58
|
+
new.send("#{property}=", nil) if value.respond_to?(:empty?) && value.empty?
|
59
|
+
end
|
60
|
+
new.eql?(old)
|
61
|
+
end
|
62
|
+
|
63
|
+
def remove(params)
|
64
|
+
raise ArgumentError unless params.is_a?(Hash)
|
65
|
+
item_to_remove = @config.metadata_model.new(params)
|
66
|
+
p [item_to_remove], @items ####
|
67
|
+
pre = @items.dup
|
68
|
+
@items.delete_if { |item| items_equal?(item, item_to_remove) }
|
69
|
+
removed = pre - @items
|
70
|
+
if removed.first
|
71
|
+
@config.logger.debug("Item #{removed.first.inspect} was removed from cart")
|
72
|
+
else
|
73
|
+
@config.logger.debug("Any item matched")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def price
|
78
|
+
@items.map { |item| item.price }.inject(:+)
|
79
|
+
end
|
80
|
+
|
81
|
+
def vat
|
82
|
+
@items.map { |item| item.vat }.inject(:+)
|
83
|
+
end
|
84
|
+
|
85
|
+
def price_without_vat
|
86
|
+
@items.map { |item| item.price_without_vat }.inject(:+)
|
87
|
+
end
|
88
|
+
|
89
|
+
def save
|
90
|
+
data = @items.map { |item| item.serialize }.join(";")
|
91
|
+
@config.logger.debug("Cart saved: #{data.inspect}")
|
92
|
+
return data
|
93
|
+
end
|
94
|
+
|
95
|
+
def each(&block)
|
96
|
+
@items.each do |item|
|
97
|
+
block.call(item)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def empty?
|
102
|
+
@items.empty?
|
103
|
+
end
|
104
|
+
|
105
|
+
def count
|
106
|
+
@items.length
|
107
|
+
end
|
108
|
+
end
|
data/lib/cart/config.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require "cart/logger_stub"
|
2
|
+
|
3
|
+
class NotInitializedError < StandardError ; end
|
4
|
+
|
5
|
+
class Cart
|
6
|
+
class << self
|
7
|
+
attr_writer :product_model, :metadata_model
|
8
|
+
def product_model
|
9
|
+
@product_model || raise(NotInitializedError)
|
10
|
+
end
|
11
|
+
|
12
|
+
def metadata_model
|
13
|
+
@metadata_model || raise(NotInitializedError)
|
14
|
+
end
|
15
|
+
|
16
|
+
def logger
|
17
|
+
@logger || raise(NotInitializedError)
|
18
|
+
end
|
19
|
+
|
20
|
+
def logger=(logger)
|
21
|
+
if logger.respond_to?(:to_proc)
|
22
|
+
@logger = LoggerStub.new(logger)
|
23
|
+
elsif logger.respond_to?(:debug)
|
24
|
+
@logger = logger
|
25
|
+
else
|
26
|
+
raise "Given logger must be callable object (Proc or Method for example) or an object with debug method."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# This class is just a wrapper which give you
|
2
|
+
# chance to give callable object instead of logger.
|
3
|
+
# See README file for more informations.
|
4
|
+
|
5
|
+
# LoggerStub.new lambda { |message| puts message }
|
6
|
+
# LoggerStub.new(method(:puts))
|
7
|
+
class LoggerStub
|
8
|
+
def initialize(callable)
|
9
|
+
@callable = callable
|
10
|
+
end
|
11
|
+
|
12
|
+
def debug(message)
|
13
|
+
@callable.call(message)
|
14
|
+
end
|
15
|
+
end
|
data/lib/cart/simple.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
require "cart/config"
|
3
|
+
|
4
|
+
class CartItem < OpenStruct
|
5
|
+
undef :id
|
6
|
+
end
|
7
|
+
|
8
|
+
# NOTE: all public methods works with products, not with their IDs
|
9
|
+
class Cart
|
10
|
+
def self.load(data)
|
11
|
+
cart = Cart.new
|
12
|
+
unless data.nil?
|
13
|
+
data = YAML::load(data)
|
14
|
+
cart.items = data.map { |id| @config.product_model.get(id) }
|
15
|
+
end
|
16
|
+
return cart
|
17
|
+
end
|
18
|
+
|
19
|
+
# returns array of products
|
20
|
+
def items
|
21
|
+
@items.map { |item| @config.product_model.get(item) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def inspect
|
25
|
+
%{<cart @items=#{@config.product_model}#{@items.map { |i| i.id }.inspect}>}
|
26
|
+
end
|
27
|
+
|
28
|
+
# takes array of products
|
29
|
+
def items=(products)
|
30
|
+
raise ArgumentError if not products.respond_to?(:each) # not just arrays
|
31
|
+
raise ArgumentError if not products.all? { |product| product.kind_of?(@config.product_model) }
|
32
|
+
@items = products.to_a
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@items = Array.new
|
37
|
+
@config = self.class
|
38
|
+
end
|
39
|
+
|
40
|
+
def save
|
41
|
+
@items.map { |product| product.id }.to_yaml
|
42
|
+
end
|
43
|
+
|
44
|
+
def add(product, count = 1)
|
45
|
+
raise ArgumentError unless product.kind_of(@config.product_model)
|
46
|
+
if item = find(product)
|
47
|
+
item.count += count
|
48
|
+
else
|
49
|
+
struct = CartItem.new
|
50
|
+
struct.id = id
|
51
|
+
struct.count = count
|
52
|
+
@items.push(struct)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# remove all products with id 1
|
57
|
+
# cart.remove(1)
|
58
|
+
# remove 2 products with id 1
|
59
|
+
# cart.remove(1, 2)
|
60
|
+
def remove(product, count = nil)
|
61
|
+
if item = find(product)
|
62
|
+
if count.nil? || item.count <= count
|
63
|
+
@items.delete(item)
|
64
|
+
elsif item.count > count
|
65
|
+
item.count = (item.count - count)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def empty?
|
71
|
+
@items.empty?
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
def find(product)
|
76
|
+
@items.find { |item| item.id.eql?(product.id) }
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', "spec_helper")
|
2
|
+
require "cart/advanced"
|
3
|
+
|
4
|
+
Cart.metadata_model = OrderItem
|
5
|
+
Cart.logger = lambda { }
|
6
|
+
|
7
|
+
describe Cart do
|
8
|
+
before(:each) do
|
9
|
+
items = 10.of { OrderItem.make(:saved_products) }
|
10
|
+
# we need to save all the products otherwise we can't
|
11
|
+
# serialize the cart (we need to know product ID)
|
12
|
+
@cart = Cart.new(*items)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe ".load" do
|
16
|
+
it "should load serialized items" do
|
17
|
+
loaded_cart = Cart.load(@cart.save)
|
18
|
+
loaded_cart.items.should eql(@cart.items)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should return empty cart if first (and only) argument is nil" do
|
22
|
+
Cart.load(nil).items.should eql(Array.new)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should return empty cart if first (and only) argument is empty string" do
|
26
|
+
Cart.load(String.new).items.should eql(Array.new)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#items" do
|
31
|
+
it "should create new cart with empty items" do
|
32
|
+
Cart.new.items.should be_empty
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should be array of order items" do
|
36
|
+
@cart.items.all? { |item| item.kind_of?(OrderItem) }.should be_true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#items=" do
|
41
|
+
it "should raise ArgumentError if first (and only) argument isn't collection" do
|
42
|
+
lambda { @cart.items = 1 }.should raise_error(ArgumentError)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should raise ArgumentError if some of items isn't integer" do
|
46
|
+
lambda { @cart.items = [1, "foo"] }.should raise_error(ArgumentError)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#save" do
|
51
|
+
it "should serialize items" do
|
52
|
+
@cart.save.should be_kind_of(String)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should be in valid format" do
|
56
|
+
pattern = 10.of {'\d+,\d,\d+'}.join(";")
|
57
|
+
@cart.save.should match(/^#{pattern}$/)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "#add" do
|
62
|
+
before do
|
63
|
+
# NOTE: when inspecting @item, u can see that
|
64
|
+
# product_id is nil althought we set the product.
|
65
|
+
# It is OK, product_id is set when product is saving
|
66
|
+
@item = OrderItem.make
|
67
|
+
@cart = Cart.new(@item)
|
68
|
+
@item.product.category = Category.generate(:standalone)
|
69
|
+
@item.product.save
|
70
|
+
@params = {:product => @item.product, :inverted => @item.inverted}
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should add one order item into cart's items by default" do
|
74
|
+
lambda { @cart.add(@params) }.should_not change { @cart.count }
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should increase count of order item if product and inverted are same" do
|
78
|
+
item = @cart.items.find { |item| item.product.eql?(@item.product) }
|
79
|
+
lambda { @cart.add(@params) }.should change { item.count }.by(1)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should add more order items into cart's items if product is different" do
|
83
|
+
another = Product.generate(:standalone)
|
84
|
+
params = @params.merge(:product => another)
|
85
|
+
lambda { @cart.add(params) }.should change { @cart.count }.by(1)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should add more order items into cart's items if product is same, but inverted is different" do
|
89
|
+
params = @params.merge(:inverted => (not @item.inverted))
|
90
|
+
lambda { @cart.add(params) }.should change { @cart.count }.by(1)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should raise error if params are not hash" do
|
94
|
+
lambda { @cart.add(@item) }.should raise_error(ArgumentError)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "#remove" do
|
99
|
+
before do
|
100
|
+
# NOTE: when inspecting @item, u can see that
|
101
|
+
# product_id is nil althought we set the product.
|
102
|
+
# It is OK, product_id is set when product is saving
|
103
|
+
@item = OrderItem.make
|
104
|
+
@cart = Cart.new(@item)
|
105
|
+
@item.product.category = Category.generate(:standalone)
|
106
|
+
@item.product.save
|
107
|
+
@params = {:product => @item.product, :inverted => @item.inverted}
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should remove order item with coresponding product and inverted options from cart's items" do
|
111
|
+
lambda { @cart.remove(@params) }.should change { @cart.count }.by(-1)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should raise error if params are not hash" do
|
115
|
+
lambda { @cart.remove(@item) }.should raise_error(ArgumentError)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "#empty?" do
|
120
|
+
it "should returns true if there aren't any items" do
|
121
|
+
Cart.new.should be_empty
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should returns false if there aren't any items" do
|
125
|
+
@cart.should_not be_empty
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "#count" do
|
130
|
+
it "should returns count of items" do
|
131
|
+
Cart.new.count.should eql(0)
|
132
|
+
@cart.count.should eql(10)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', "spec_helper")
|
2
|
+
require "cart/config"
|
3
|
+
|
4
|
+
class Logger
|
5
|
+
def debug(message)
|
6
|
+
puts(message)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Cart do
|
11
|
+
describe "#product_model & #product_model=" do
|
12
|
+
it "should returns given model if configured" do
|
13
|
+
Cart.product_model = Product
|
14
|
+
Cart.product_model.should eql(Product)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should raise an exception unless configured" do
|
18
|
+
Cart.product_model = nil
|
19
|
+
lambda { Cart.product_model }.should raise_error(NotInitializedError)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#metadata_model & #metadata_model=" do
|
24
|
+
it "should returns given model if configured" do
|
25
|
+
Cart.metadata_model = Product
|
26
|
+
Cart.metadata_model.should eql(Product)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should raise an exception unless configured" do
|
30
|
+
Cart.metadata_model = nil
|
31
|
+
lambda { Cart.metadata_model }.should raise_error(NotInitializedError)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#logger & #logger=" do
|
36
|
+
it "should works with lambdas" do
|
37
|
+
Cart.logger = Logger.new
|
38
|
+
Cart.logger.should be_kind_of(Logger)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should raise exception when object is not callable and not respond to debug method" do
|
42
|
+
lambda { Cart.logger = Class.new }.should raise_error
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should works with lambdas" do
|
46
|
+
Cart.logger = lambda { |msg| puts(msg) }
|
47
|
+
Cart.logger.should be_kind_of(LoggerStub)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should works with references to method" do
|
51
|
+
Cart.logger = method(:puts)
|
52
|
+
Cart.logger.should be_kind_of(LoggerStub)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', "spec_helper")
|
2
|
+
require "cart/logger_stub"
|
3
|
+
|
4
|
+
describe LoggerStub do
|
5
|
+
it "should works with lambdas" do
|
6
|
+
# just return the message
|
7
|
+
proc = lambda { |message| message }
|
8
|
+
logger = LoggerStub.new(proc)
|
9
|
+
logger.debug("Hey, it works!").should eql("Hey, it works!")
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should works with methods" do
|
13
|
+
# just return the message
|
14
|
+
def just_return(message) ; message ; end
|
15
|
+
logger = LoggerStub.new(method(:just_return))
|
16
|
+
logger.debug("Hey, it works!").should eql("Hey, it works!")
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', "spec_helper")
|
2
|
+
require "cart/simple"
|
3
|
+
|
4
|
+
Cart.product_model = Product
|
5
|
+
Cart.logger = lambda { }
|
6
|
+
|
7
|
+
describe Cart do
|
8
|
+
before(:each) do
|
9
|
+
@cart = Cart.new
|
10
|
+
10.times { Product.create }
|
11
|
+
@cart.items = Product.all
|
12
|
+
end
|
13
|
+
|
14
|
+
describe ".load" do
|
15
|
+
it "should load serialized items" do
|
16
|
+
pending "works, but this spec do not"
|
17
|
+
loaded_cart = Cart.load(@cart.save)
|
18
|
+
loaded_cart.items.should eql(@cart.items)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should return empty cart if first (and only) argument is nil" do
|
22
|
+
Cart.load(nil).items.should eql(Array.new)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#items" do
|
27
|
+
it "should create new cart with empty items" do
|
28
|
+
Cart.new.items.should be_empty
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should be array of products" do
|
32
|
+
pending "works, but this spec do not"
|
33
|
+
@cart.items.all? { |item| item.kind_of?(Product) }.should be_true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#items=" do
|
38
|
+
it "should raise ArgumentError if first (and only) argument isn't collection" do
|
39
|
+
lambda { @cart.items = 1 }.should raise_error(ArgumentError)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should raise ArgumentError if some of items isn't integer" do
|
43
|
+
lambda { @cart.items = [1, "foo"] }.should raise_error(ArgumentError)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#inspect" do
|
48
|
+
it "should show id of products in items"
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "#save" do
|
52
|
+
it "should serialize items" do
|
53
|
+
@cart.save.should be_kind_of(String)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should be regular YAML" do
|
57
|
+
lambda { YAML::load(@cart.save) }.should_not raise_error
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "#add" do
|
62
|
+
it "should add one product into cart's items by default"
|
63
|
+
it "should add more product into cart's items when optional argument given"
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "#remove" do
|
67
|
+
it "should remove one product into cart's items by default"
|
68
|
+
it "should remove more product into cart's items when optional argument given"
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#empty?" do
|
72
|
+
it "should returns true if there aren't any items" do
|
73
|
+
Cart.new.should be_empty
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should returns false if there aren't any items" do
|
77
|
+
@cart.should_not be_empty
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/spec/factories.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
class Product
|
2
|
+
include DataMapper::Resource
|
3
|
+
property :id, Serial
|
4
|
+
end
|
5
|
+
|
6
|
+
class OrderItem
|
7
|
+
include DataMapper::Resource
|
8
|
+
property :id, Serial
|
9
|
+
property :size, Enum["10x20", "20x30", "25x40"]
|
10
|
+
property :inverted, Boolean
|
11
|
+
|
12
|
+
def serialize
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def restore(data)
|
17
|
+
end
|
18
|
+
|
19
|
+
def fixture(params = Hash.new)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
$: << File.dirname(__FILE__) + "/../lib"
|
2
|
+
|
3
|
+
require "dm-core"
|
4
|
+
require "dm-types"
|
5
|
+
require File.dirname(__FILE__) + "/factories"
|
6
|
+
|
7
|
+
DataMapper.setup(:default, "sqlite3::memory")
|
8
|
+
|
9
|
+
Spec::Runner.configure do |config|
|
10
|
+
config.before(:each) do
|
11
|
+
DataMapper.auto_migrate!
|
12
|
+
end
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cart
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Jakub \xC5\xA0\xC5\xA5astn\xC3\xBD aka Botanicus"
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain:
|
11
|
+
date: 2009-11-26 00:00:00 +00:00
|
12
|
+
default_executable:
|
13
|
+
dependencies: []
|
14
|
+
|
15
|
+
description: Cart is framework agnostic solution for shopping cart. There are two existing imlementations. First is *Cart::Simple* which is just basic cart which can store only ID of products without any metadata. The second is *Cart::Advanced* and it is good when you have not just product, but also some metadata as size or color of product etc.
|
16
|
+
email: knava.bestvinensis@gmail.com
|
17
|
+
executables: []
|
18
|
+
|
19
|
+
extensions: []
|
20
|
+
|
21
|
+
extra_rdoc_files: []
|
22
|
+
|
23
|
+
files:
|
24
|
+
- cart.gemspec
|
25
|
+
- lib/cart/advanced.rb
|
26
|
+
- lib/cart/config.rb
|
27
|
+
- lib/cart/logger_stub.rb
|
28
|
+
- lib/cart/simple.rb
|
29
|
+
- LICENSE
|
30
|
+
- Rakefile
|
31
|
+
- README.textile
|
32
|
+
- spec/cart/advanced_spec.rb
|
33
|
+
- spec/cart/config_spec.rb
|
34
|
+
- spec/cart/logger_stub_spec.rb
|
35
|
+
- spec/cart/simple_spec.rb
|
36
|
+
- spec/factories.rb
|
37
|
+
- spec/spec.opts
|
38
|
+
- spec/spec_helper.rb
|
39
|
+
- TODO
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: http://github.com/botanicus/cart
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
requirements: []
|
62
|
+
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 1.3.5
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: Cart is framework agnostic solution for shopping cart.
|
68
|
+
test_files: []
|
69
|
+
|