acts_as_shopping_cart 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (27) hide show
  1. data/.gitignore +1 -0
  2. data/README.markdown +50 -4
  3. data/acts_as_shopping_cart.gemspec +2 -0
  4. data/features/shopping_cart.feature +46 -0
  5. data/features/step_definitions/product_steps.rb +3 -0
  6. data/features/step_definitions/shopping_cart_steps.rb +69 -0
  7. data/features/support/env.rb +8 -0
  8. data/features/support/rails_env.rb +24 -0
  9. data/lib/active_record/acts/shopping_cart.rb +4 -3
  10. data/lib/active_record/acts/shopping_cart/cart/instance_methods.rb +70 -0
  11. data/lib/active_record/acts/shopping_cart/item/instance_methods.rb +58 -0
  12. data/lib/active_record/acts/shopping_cart_item.rb +2 -2
  13. data/lib/active_record/acts/shopping_cart_item/{cart_item_instance_methods.rb → instance_methods.rb} +4 -4
  14. data/lib/acts_as_shopping_cart.rb +28 -6
  15. data/lib/acts_as_shopping_cart/schema.rb +17 -0
  16. data/lib/acts_as_shopping_cart/version.rb +1 -1
  17. data/spec/active_record/acts/shopping_cart/cart/instance_methods_spec.rb +155 -0
  18. data/spec/active_record/acts/shopping_cart/item/instance_methods_spec.rb +134 -0
  19. data/spec/active_record/acts/shopping_cart_item/instance_methods_spec.rb +38 -0
  20. data/spec/spec_helper.rb +2 -51
  21. metadata +52 -21
  22. data/lib/active_record/acts/shopping_cart/cart_instance_methods.rb +0 -67
  23. data/lib/active_record/acts/shopping_cart/item_instance_methods.rb +0 -64
  24. data/spec/active_record/acts/shopping_cart/cart_instance_methods_spec.rb +0 -220
  25. data/spec/active_record/acts/shopping_cart/cart_item_instance_methods_spec.rb +0 -46
  26. data/spec/active_record/acts/shopping_cart/item_instance_methods_spec.rb +0 -125
  27. data/spec/acts_as_shopping_cart_spec.rb +0 -16
data/.gitignore CHANGED
@@ -4,3 +4,4 @@ Gemfile.lock
4
4
  pkg/*
5
5
  .rvmrc
6
6
  coverage
7
+ coverage.features
data/README.markdown CHANGED
@@ -94,6 +94,48 @@ You can find out about the total using the _total_ method:
94
94
 
95
95
  @cart.total # => 99.99
96
96
 
97
+ ### Taxes
98
+
99
+ Taxes by default are calculated by multiplying subtotal times 8.25
100
+
101
+ If you want to change the way taxes are calculated, override the taxes
102
+ method on your class that acts_as_shopping_cart.
103
+
104
+ Example:
105
+
106
+ class ShoppingCart < ActiveRecord::Base
107
+ acts_as_shopping_cart
108
+
109
+ def taxes
110
+ (subtotal - 10) * 8.3
111
+ end
112
+ end
113
+
114
+ If you just want to update the percentage, just override the tax_pct
115
+ method.
116
+
117
+ class ShoppingCart < ActiveRecord::Base
118
+ acts_as_shopping_cart
119
+
120
+ def tax_pct
121
+ 3.5
122
+ end
123
+ end
124
+
125
+ ### Shipping Cost
126
+
127
+ Shipping cost will be added to the total. By default its calculated as
128
+ 0, but you can just override the shipping_cost method on your cart
129
+ class depending on your needs.
130
+
131
+ class ShoppingCart < ActiveRecord::Base
132
+ acts_as_shopping_cart
133
+
134
+ def shipping_cost
135
+ 5 # defines a flat $5 rate
136
+ end
137
+ end
138
+
97
139
  ### Total unique items
98
140
 
99
141
  You can find out how many unique items you have on your cart using the _total_unique_items_ method.
@@ -118,8 +160,12 @@ Run rspec
118
160
 
119
161
  rspec spec
120
162
 
121
- ## TODO
163
+ Run cucumber features
164
+
165
+ bundle exec cucumber
166
+
167
+ # About the Author
122
168
 
123
- * Finish this document
124
- * Test it on Rails 2
125
- * Some more useful methods, like @cart.quantity_for(@product), @cart.price_for(@product)
169
+ [Crowd Interactive](http://www.crowdint.com) is an American web design and development company that happens to work in Colima, Mexico.
170
+ We specialize in building and growing online retail stores. We don’t work with everyone – just companies we believe in. Call us today to see if there’s a fit.
171
+ Find more info [here](http://www.crowdint.com)!
@@ -19,6 +19,8 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ["lib"]
20
20
 
21
21
  s.add_dependency 'activerecord', '~> 3.0'
22
+ s.add_development_dependency "cucumber"
23
+ s.add_development_dependency "database_cleaner"
22
24
  s.add_development_dependency "rspec"
23
25
  s.add_development_dependency "sqlite3"
24
26
  s.add_development_dependency "simplecov"
@@ -0,0 +1,46 @@
1
+ Feature: Shopping Cart
2
+
3
+ Background:
4
+ Given a product "Apple" exists
5
+ And a shopping cart exists
6
+
7
+ Scenario: Cart totals
8
+ When I add product "Apple" to cart with price "99.99"
9
+ Then the subtotal for the cart should be "99.99"
10
+ And the total for the cart should be "108.24"
11
+ And the total unique items on the cart should be "1"
12
+
13
+ Scenario: Add a product to cart twice
14
+ When I add product "Apple" to cart with price "99.99"
15
+ And I add product "Apple" to cart with price "99.99"
16
+ Then the subtotal for the cart should be "199.98"
17
+ Then the total for the cart should be "216.48"
18
+ And the total unique items on the cart should be "2"
19
+
20
+ Scenario: Remove products from cart
21
+ Given I add 3 "Apple" products to cart with price "99.99"
22
+ When I remove 1 "Apple" unit from cart
23
+ Then the total unique items on the cart should be "2"
24
+ When I remove 99 "Apple" units from cart
25
+ Then the total unique items on the cart should be "0"
26
+ And cart should be empty
27
+
28
+ Scenario: Totals for a single item
29
+ Given I add 3 "Apple" products to cart with price "99.99"
30
+ Then the subtotal for "Apple" on the cart should be "299.97"
31
+ And the quantity for "Apple" on the cart should be "3"
32
+ And the price for "Apple" on the cart should be "99.99"
33
+
34
+ Scenario: Subtotal for a product that is not on cart
35
+ Then the subtotal for "Apple" on the cart should be "0"
36
+
37
+ Scenario: Update the quantity of a cart item
38
+ Given I add 99 "Apple" products to cart with price "99.99"
39
+ When I update the "Apple" quantity to "2"
40
+ Then the quantity for "Apple" on the cart should be "2"
41
+
42
+ Scenario: Update the price of a cart item
43
+ Given I add 99 "Apple" products to cart with price "99.99"
44
+ When I update the "Apple" price to "10.99"
45
+ Then the price for "Apple" on the cart should be "10.99"
46
+
@@ -0,0 +1,3 @@
1
+ Given /^a product "([^"]*)" exists$/ do |name|
2
+ Product.create(:name => name)
3
+ end
@@ -0,0 +1,69 @@
1
+ Given /^a shopping cart exists$/ do
2
+ @cart = ShoppingCart.create
3
+ end
4
+
5
+ Then /^the total for the cart should be "([^"]*)"$/ do |total|
6
+ @cart.reload
7
+ @cart.total.should eq(total.to_f)
8
+ end
9
+
10
+ Then /^the subtotal for the cart should be "([^"]*)"$/ do |subtotal|
11
+ @cart.reload
12
+ @cart.subtotal.should eq(subtotal.to_f)
13
+ end
14
+
15
+ When /^I add product "([^"]*)" to cart with price "([^"]*)"$/ do |product_name, price|
16
+ product = Product.find_by_name(product_name)
17
+ @cart.add(product, price)
18
+ end
19
+
20
+ Then /^the total unique items on the cart should be "([^"]*)"$/ do |total|
21
+ @cart.reload
22
+ @cart.total_unique_items.should eq(total.to_i)
23
+ end
24
+
25
+ When /^I remove (\d+) "([^"]*)" unit(s?) from cart$/ do |quantity, product_name, plural|
26
+ @cart.reload
27
+ product = Product.find_by_name(product_name)
28
+ @cart.remove(product, quantity.to_i)
29
+ end
30
+
31
+ Then /^cart should be empty$/ do
32
+ @cart.reload
33
+ @cart.cart_items.should be_empty
34
+ end
35
+
36
+ Given /^I add (\d+) "([^"]*)" products to cart with price "([^"]*)"$/ do |quantity, product_name, price|
37
+ product = Product.find_by_name(product_name)
38
+ @cart.add(product, price.to_f, quantity.to_i)
39
+ end
40
+
41
+ Then /^the subtotal for "([^"]*)" on the cart should be "([^"]*)"$/ do |product_name, subtotal|
42
+ @cart.reload
43
+ product = Product.find_by_name(product_name)
44
+ @cart.subtotal_for(product).should eq(subtotal.to_f)
45
+ end
46
+
47
+ Then /^the quantity for "([^"]*)" on the cart should be "([^"]*)"$/ do |product_name, quantity|
48
+ @cart.reload
49
+ product = Product.find_by_name(product_name)
50
+ @cart.quantity_for(product).should eq(quantity.to_f)
51
+ end
52
+
53
+ Then /^the price for "([^"]*)" on the cart should be "([^"]*)"$/ do |product_name, price|
54
+ @cart.reload
55
+ product = Product.find_by_name(product_name)
56
+ @cart.price_for(product).should eq(price.to_f)
57
+ end
58
+
59
+ When /^I update the "([^"]*)" quantity to "([^"]*)"$/ do |product_name, quantity|
60
+ @cart.reload
61
+ product = Product.find_by_name(product_name)
62
+ @cart.update_quantity_for(product, quantity.to_i)
63
+ end
64
+
65
+ When /^I update the "([^"]*)" price to "([^"]*)"$/ do |product_name, price|
66
+ @cart.reload
67
+ product = Product.find_by_name(product_name)
68
+ @cart.update_price_for(product, price.to_f)
69
+ end
@@ -0,0 +1,8 @@
1
+ require 'active_record'
2
+ require 'database_cleaner'
3
+ require 'acts_as_shopping_cart'
4
+
5
+ require 'simplecov'
6
+
7
+ SimpleCov.coverage_dir 'coverage.features'
8
+ SimpleCov.start
@@ -0,0 +1,24 @@
1
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
2
+
3
+ ActiveRecord::Schema.define(:version => 1) do
4
+ create_table :shopping_carts
5
+ create_table :shopping_cart_items do |t|
6
+ t.shopping_cart_item_fields
7
+ end
8
+
9
+ create_table :products do |t|
10
+ t.string :name
11
+ end
12
+ end
13
+
14
+ class Product < ActiveRecord::Base
15
+
16
+ end
17
+
18
+ class ShoppingCart < ActiveRecord::Base
19
+ acts_as_shopping_cart
20
+ end
21
+
22
+ class ShoppingCartItem < ActiveRecord::Base
23
+ acts_as_shopping_cart_item
24
+ end
@@ -10,14 +10,15 @@ module ActiveRecord
10
10
  # Prepares the class to act as a cart.
11
11
  #
12
12
  # Receives as a parameter the name of the class that will hold the items
13
- #
13
+ #
14
14
  # Example:
15
15
  #
16
16
  # acts_as_shopping_cart :cart_item
17
17
  #
18
18
  #
19
19
  def acts_as_shopping_cart_using(item_class)
20
- self.send :include, ActiveRecord::Acts::ShoppingCart::InstanceMethods
20
+ self.send :include, ActiveRecord::Acts::ShoppingCart::Cart::InstanceMethods
21
+ self.send :include, ActiveRecord::Acts::ShoppingCart::Item::InstanceMethods
21
22
  has_many :cart_items, :class_name => item_class.to_s.classify, :as => :owner
22
23
  end
23
24
 
@@ -32,4 +33,4 @@ module ActiveRecord
32
33
  end
33
34
  end
34
35
  end
35
- end
36
+ end
@@ -0,0 +1,70 @@
1
+ module ActiveRecord
2
+ module Acts
3
+ module ShoppingCart
4
+ module Cart
5
+ module InstanceMethods
6
+ #
7
+ # Adds a product to the cart
8
+ #
9
+ def add(object, price, quantity = 1)
10
+ cart_item = item_for(object)
11
+
12
+ unless cart_item
13
+ cart_items.create(:item => object, :price => price, :quantity => quantity)
14
+ else
15
+ cart_item.quantity = (cart_item.quantity + quantity)
16
+ cart_item.save
17
+ end
18
+ end
19
+
20
+ #
21
+ # Remove an item from the cart
22
+ #
23
+ def remove(object, quantity = 1)
24
+ if cart_item = item_for(object)
25
+ if cart_item.quantity <= quantity
26
+ cart_item.delete
27
+ else
28
+ cart_item.quantity = (cart_item.quantity - quantity)
29
+ cart_item.save
30
+ end
31
+ end
32
+ end
33
+
34
+ #
35
+ # Returns the subtotal by summing the price times quantity for all the items in the cart
36
+ #
37
+ def subtotal
38
+ ("%.2f" % cart_items.inject(0) { |sum, item| sum += (item.price * item.quantity) }).to_f
39
+ end
40
+
41
+ def shipping_cost
42
+ 0
43
+ end
44
+
45
+ def taxes
46
+ subtotal * self.tax_pct * 0.01
47
+ end
48
+
49
+ def tax_pct
50
+ 8.25
51
+ end
52
+
53
+ #
54
+ # Returns the total by summing the subtotal, taxes and shipping_cost
55
+ #
56
+ def total
57
+ ("%.2f" % (self.subtotal + self.taxes + self.shipping_cost)).to_f
58
+ end
59
+
60
+ #
61
+ # Return the number of unique items in the cart
62
+ #
63
+ def total_unique_items
64
+ cart_items.inject(0) { |sum, item| sum += item.quantity }
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,58 @@
1
+ module ActiveRecord
2
+ module Acts
3
+ module ShoppingCart
4
+ module Item
5
+ module InstanceMethods
6
+
7
+ #
8
+ # Returns the cart item for the specified object
9
+ #
10
+ def item_for(object)
11
+ cart_items.where(:item_id => object.id).first
12
+ end
13
+
14
+ #
15
+ # Returns the subtotal of a specified item by multiplying the quantity times
16
+ # the price of the item.
17
+ #
18
+ def subtotal_for(object)
19
+ item = item_for(object)
20
+ item ? item.subtotal : 0
21
+ end
22
+
23
+ #
24
+ # Returns the quantity of the specified object
25
+ #
26
+ def quantity_for(object)
27
+ item = item_for(object)
28
+ item ? item.quantity : 0
29
+ end
30
+
31
+ #
32
+ # Updates the quantity of the specified object
33
+ #
34
+ def update_quantity_for(object, new_quantity)
35
+ item = item_for(object)
36
+ item.update_quantity(new_quantity) if item
37
+ end
38
+
39
+ #
40
+ # Returns the price of the specified object
41
+ #
42
+ def price_for(object)
43
+ item = item_for(object)
44
+ item ? item.price : 0
45
+ end
46
+
47
+ #
48
+ # Updates the price of the specified object
49
+ #
50
+ def update_price_for(object, new_price)
51
+ item = item_for(object)
52
+ item.update_price(new_price) if item
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  # Prepares the class to act as a cart item.
11
11
  #
12
12
  # Receives as a parameter the name of the class that acts as a cart
13
- #
13
+ #
14
14
  # Example:
15
15
  #
16
16
  # acts_as_shopping_cart_item :cart
@@ -33,4 +33,4 @@ module ActiveRecord
33
33
  end
34
34
  end
35
35
  end
36
- end
36
+ end
@@ -4,9 +4,9 @@ module ActiveRecord
4
4
  module InstanceMethods
5
5
  #
6
6
  # Returns the subtotal, multiplying the quantity times the price of the item.
7
- #
7
+ #
8
8
  def subtotal
9
- self.quantity * self.price
9
+ ("%.2f" % (self.quantity * self.price)).to_f
10
10
  end
11
11
 
12
12
  #
@@ -16,7 +16,7 @@ module ActiveRecord
16
16
  self.quantity = new_quantity
17
17
  self.save
18
18
  end
19
-
19
+
20
20
  #
21
21
  # Updates the price of the item
22
22
  #
@@ -27,4 +27,4 @@ module ActiveRecord
27
27
  end
28
28
  end
29
29
  end
30
- end
30
+ end
@@ -1,10 +1,32 @@
1
1
  require 'acts_as_shopping_cart/version'
2
2
  require 'active_record/acts/shopping_cart'
3
- require 'active_record/acts/shopping_cart/cart_instance_methods'
4
- require 'active_record/acts/shopping_cart/item_instance_methods'
5
-
6
3
  require 'active_record/acts/shopping_cart_item'
7
- require 'active_record/acts/shopping_cart_item/cart_item_instance_methods'
8
4
 
9
- ActiveRecord::Base.send :extend, ActiveRecord::Acts::ShoppingCart::ClassMethods
10
- ActiveRecord::Base.send :extend, ActiveRecord::Acts::ShoppingCartItem::ClassMethods
5
+ # require 'active_record/acts/shopping_cart/cart_instance_methods'
6
+ # require 'active_record/acts/shopping_cart/item_instance_methods'
7
+
8
+ # require 'active_record/acts/shopping_cart_item'
9
+ # require 'active_record/acts/shopping_cart_item/cart_item_instance_methods'
10
+
11
+ require 'acts_as_shopping_cart/schema'
12
+
13
+ module ActiveRecord
14
+ module Acts
15
+ module ShoppingCart
16
+ module Cart
17
+ autoload :InstanceMethods, 'active_record/acts/shopping_cart/cart/instance_methods'
18
+ end
19
+
20
+ module Item
21
+ autoload :InstanceMethods, 'active_record/acts/shopping_cart/item/instance_methods'
22
+ end
23
+ end
24
+
25
+ module ShoppingCartItem
26
+ autoload :InstanceMethods, 'active_record/acts/shopping_cart_item/instance_methods'
27
+ end
28
+ end
29
+ end
30
+
31
+ ActiveRecord::Base.send :include, ActiveRecord::Acts::ShoppingCart
32
+ ActiveRecord::Base.send :include, ActiveRecord::Acts::ShoppingCartItem