bodega 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +85 -0
- data/MIT-LICENSE +20 -0
- data/README.md +16 -0
- data/Rakefile +29 -0
- data/VERSION +1 -0
- data/app/assets/images/bodega/.gitkeep +0 -0
- data/app/assets/javascripts/bodega/application.js +0 -0
- data/app/assets/stylesheets/bodega/application.css +13 -0
- data/app/controllers/bodega/application_controller.rb +4 -0
- data/app/controllers/bodega/orders_controller.rb +51 -0
- data/app/helpers/bodega/application_helper.rb +11 -0
- data/app/helpers/bodega/cart_helper.rb +28 -0
- data/app/models/bodega/order.rb +28 -0
- data/app/models/bodega/order_product.rb +47 -0
- data/app/models/bodega/product.rb +33 -0
- data/app/views/bodega/orders/new.html.erb +29 -0
- data/app/views/bodega/orders/show.html.erb +1 -0
- data/bodega.gemspec +84 -0
- data/config/routes.rb +16 -0
- data/db/migrate/20121111170337_create_bodega_orders.rb +13 -0
- data/db/migrate/20121111170420_create_bodega_order_products.rb +13 -0
- data/lib/bodega.rb +17 -0
- data/lib/bodega/engine.rb +20 -0
- data/lib/bodega/monetize.rb +15 -0
- data/lib/bodega/payment_method.rb +10 -0
- data/lib/bodega/payment_method/base.rb +31 -0
- data/lib/bodega/payment_method/paypal.rb +40 -0
- data/lib/bodega/version.rb +3 -0
- data/lib/generators/bodega/install/install_generator.rb +0 -0
- data/lib/generators/bodega/product/USAGE +9 -0
- data/lib/generators/bodega/product/product_generator.rb +19 -0
- data/lib/generators/bodega/product/templates/migration.rb +12 -0
- data/lib/generators/bodega/productize/USAGE +8 -0
- data/lib/generators/bodega/productize/productize_generator.rb +19 -0
- data/lib/generators/bodega/productize/templates/migration.rb +10 -0
- data/lib/tasks/bodega_tasks.rake +4 -0
- data/script/rails +8 -0
- metadata +137 -0
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.3@bodega --create
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
actionpack (3.2.9)
|
5
|
+
activemodel (= 3.2.9)
|
6
|
+
activesupport (= 3.2.9)
|
7
|
+
builder (~> 3.0.0)
|
8
|
+
erubis (~> 2.7.0)
|
9
|
+
journey (~> 1.0.4)
|
10
|
+
rack (~> 1.4.0)
|
11
|
+
rack-cache (~> 1.2)
|
12
|
+
rack-test (~> 0.6.1)
|
13
|
+
sprockets (~> 2.2.1)
|
14
|
+
activemodel (3.2.9)
|
15
|
+
activesupport (= 3.2.9)
|
16
|
+
builder (~> 3.0.0)
|
17
|
+
activesupport (3.2.9)
|
18
|
+
i18n (~> 0.6)
|
19
|
+
multi_json (~> 1.0)
|
20
|
+
builder (3.0.4)
|
21
|
+
coderay (1.0.8)
|
22
|
+
configurator2 (0.1.1)
|
23
|
+
diff-lcs (1.1.3)
|
24
|
+
erubis (2.7.0)
|
25
|
+
git (1.2.5)
|
26
|
+
hike (1.2.1)
|
27
|
+
i18n (0.6.1)
|
28
|
+
jeweler (1.8.4)
|
29
|
+
bundler (~> 1.0)
|
30
|
+
git (>= 1.2.5)
|
31
|
+
rake
|
32
|
+
rdoc
|
33
|
+
journey (1.0.4)
|
34
|
+
json (1.7.5)
|
35
|
+
method_source (0.8.1)
|
36
|
+
multi_json (1.5.0)
|
37
|
+
pry (0.9.10)
|
38
|
+
coderay (~> 1.0.5)
|
39
|
+
method_source (~> 0.8)
|
40
|
+
slop (~> 3.3.1)
|
41
|
+
rack (1.4.1)
|
42
|
+
rack-cache (1.2)
|
43
|
+
rack (>= 0.4)
|
44
|
+
rack-ssl (1.3.2)
|
45
|
+
rack
|
46
|
+
rack-test (0.6.2)
|
47
|
+
rack (>= 1.0)
|
48
|
+
railties (3.2.9)
|
49
|
+
actionpack (= 3.2.9)
|
50
|
+
activesupport (= 3.2.9)
|
51
|
+
rack-ssl (~> 1.3.2)
|
52
|
+
rake (>= 0.8.7)
|
53
|
+
rdoc (~> 3.4)
|
54
|
+
thor (>= 0.14.6, < 2.0)
|
55
|
+
rake (0.9.2.2)
|
56
|
+
rdoc (3.12)
|
57
|
+
json (~> 1.4)
|
58
|
+
rspec-core (2.12.1)
|
59
|
+
rspec-expectations (2.12.0)
|
60
|
+
diff-lcs (~> 1.1.3)
|
61
|
+
rspec-mocks (2.12.0)
|
62
|
+
rspec-rails (2.12.0)
|
63
|
+
actionpack (>= 3.0)
|
64
|
+
activesupport (>= 3.0)
|
65
|
+
railties (>= 3.0)
|
66
|
+
rspec-core (~> 2.12.0)
|
67
|
+
rspec-expectations (~> 2.12.0)
|
68
|
+
rspec-mocks (~> 2.12.0)
|
69
|
+
slop (3.3.3)
|
70
|
+
sprockets (2.2.2)
|
71
|
+
hike (~> 1.2)
|
72
|
+
multi_json (~> 1.0)
|
73
|
+
rack (~> 1.0)
|
74
|
+
tilt (~> 1.1, != 1.3.0)
|
75
|
+
thor (0.16.0)
|
76
|
+
tilt (1.3.3)
|
77
|
+
|
78
|
+
PLATFORMS
|
79
|
+
ruby
|
80
|
+
|
81
|
+
DEPENDENCIES
|
82
|
+
configurator2 (>= 0.1.1)
|
83
|
+
jeweler (= 1.8.4)
|
84
|
+
pry
|
85
|
+
rspec-rails
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 YOURNAME
|
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.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Bodega
|
2
|
+
|
3
|
+
**Bodega allows any ActiveRecord::Base subclass to be purchased with a few simple steps:**
|
4
|
+
|
5
|
+
1. Install Bodega (add `gem 'bodega'` to your Gemfile and bundle)
|
6
|
+
2. For existing models:
|
7
|
+
1. `rails g bodega:productize existing_class_name`
|
8
|
+
2. Add `include Bodega::Product` to your class definition
|
9
|
+
3. For new models:
|
10
|
+
1. `rails g bodega:product new_class_name`
|
11
|
+
4. Add `mount Bodega::Engine => 'cart'` to your `config/routes.rb` file
|
12
|
+
5. Profit (literally, for once)
|
13
|
+
|
14
|
+
## WIP
|
15
|
+
|
16
|
+
This is a work-in-progress and is currently only in use on [womannyc.com](http://www.womannyc.com). Play with it if you want. It's not that exciting; shut up.
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'bundler'
|
6
|
+
|
7
|
+
begin
|
8
|
+
Bundler.setup(:default, :development)
|
9
|
+
rescue Bundler::BundlerError => e
|
10
|
+
$stderr.puts e.message
|
11
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
12
|
+
exit e.status_code
|
13
|
+
end
|
14
|
+
require 'rake'
|
15
|
+
|
16
|
+
require 'jeweler'
|
17
|
+
Jeweler::Tasks.new do |gem|
|
18
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
19
|
+
gem.name = "bodega"
|
20
|
+
gem.homepage = "http://github.com/flipsasser/bodega"
|
21
|
+
gem.license = "MIT"
|
22
|
+
gem.summary = %Q{Bodega adds checkout logic to any model in your app!}
|
23
|
+
gem.description = %Q{Bodega adds checkout logic to any model in your app!}
|
24
|
+
gem.email = "flip@x451.com"
|
25
|
+
gem.authors = ["Flip Sasser"]
|
26
|
+
# dependencies defined in Gemfile
|
27
|
+
end
|
28
|
+
Jeweler::RubygemsDotOrgTasks.new
|
29
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
File without changes
|
File without changes
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
9
|
+
* compiled file, but it's generally better to create a new file per style scope.
|
10
|
+
*
|
11
|
+
*= require_self
|
12
|
+
*= require_tree .
|
13
|
+
*/
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class Bodega::OrdersController < ApplicationController
|
2
|
+
helper 'bodega/cart'
|
3
|
+
include Bodega::CartHelper
|
4
|
+
include Bodega::PaymentMethod
|
5
|
+
|
6
|
+
before_filter :find_order, only: [:show, :update]
|
7
|
+
|
8
|
+
def add
|
9
|
+
if product = params[:product]
|
10
|
+
update_cart(product)
|
11
|
+
end
|
12
|
+
redirect_to root_path
|
13
|
+
end
|
14
|
+
|
15
|
+
def complete
|
16
|
+
current_order.payment_id = payment_method.complete!
|
17
|
+
current_order.save!
|
18
|
+
redirect_to order_path(current_order)
|
19
|
+
end
|
20
|
+
|
21
|
+
def create
|
22
|
+
params[:products].each do |product|
|
23
|
+
update_cart(product)
|
24
|
+
end
|
25
|
+
if params[:checkout]
|
26
|
+
redirect_to payment_method.checkout_url(complete_url, root_url)
|
27
|
+
else
|
28
|
+
render :new
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
def find_order
|
34
|
+
raise ActiveRecord::NotFound unless @order = Bodega::Order.where(id: params[:order_id] || params[:id]).first
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_cart(product)
|
38
|
+
product_id = "#{product[:type]}.#{product[:id]}"
|
39
|
+
if product[:remove]
|
40
|
+
current_products.delete product_id
|
41
|
+
else
|
42
|
+
if current = current_products[product_id]
|
43
|
+
current_quantity = current[:quantity].to_i
|
44
|
+
else
|
45
|
+
current_quantity = 0
|
46
|
+
end
|
47
|
+
new_quantity = product[:quantity] ? product[:quantity] : current_quantity + 1
|
48
|
+
current_products[product_id] = product.merge(quantity: new_quantity)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Bodega
|
2
|
+
module ApplicationHelper
|
3
|
+
def button_to_cart(product, label = 'Add to Cart')
|
4
|
+
form_tag(bodega.add_path) do
|
5
|
+
hidden_field_tag('product[type]', product.class) +
|
6
|
+
hidden_field_tag('product[id]', product.id) +
|
7
|
+
button_tag(label)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Bodega
|
2
|
+
module CartHelper
|
3
|
+
protected
|
4
|
+
def current_order
|
5
|
+
@current_order ||= Bodega::Order.new.tap do |order|
|
6
|
+
#begin
|
7
|
+
if Bodega.config.customer_method
|
8
|
+
order.customer = send(Bodega.config.customer_method)
|
9
|
+
end
|
10
|
+
#rescue NoMethodError
|
11
|
+
raise "Please configure Bodega.config.customer_method to point to a valid method for accessing a customer record (default: current_user)"
|
12
|
+
#end
|
13
|
+
order.order_products = current_products.map do |type, product|
|
14
|
+
product = product.symbolize_keys
|
15
|
+
OrderProduct.new do |order_product|
|
16
|
+
order_product.product_type = product[:type]
|
17
|
+
order_product.product_id = product[:id]
|
18
|
+
order_product.quantity = product[:quantity]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def current_products
|
25
|
+
session[:bodega_products] ||= {}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Bodega
|
2
|
+
class Order < ActiveRecord::Base
|
3
|
+
extend Bodega::Monetize
|
4
|
+
|
5
|
+
before_create :set_identifier
|
6
|
+
|
7
|
+
belongs_to :customer, polymorphic: true
|
8
|
+
has_many :order_products, class_name: 'Bodega::OrderProduct', dependent: :destroy
|
9
|
+
has_many :products, through: :order_products
|
10
|
+
|
11
|
+
monetize :subtotal
|
12
|
+
monetize :tax
|
13
|
+
monetize :total
|
14
|
+
|
15
|
+
def subtotal
|
16
|
+
order_products.inject(0) {|sum, order_product| sum += order_product.subtotal }
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_param
|
20
|
+
identifier
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
def set_identifier
|
25
|
+
self.identifier = "#{Time.now.to_i}--#{rand(12)}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Bodega
|
2
|
+
class OrderProduct < ActiveRecord::Base
|
3
|
+
extend Bodega::Monetize
|
4
|
+
|
5
|
+
belongs_to :order, class_name: 'Bodega::Order'
|
6
|
+
belongs_to :product, polymorphic: true
|
7
|
+
|
8
|
+
delegate :price, to: :product
|
9
|
+
|
10
|
+
monetize :subtotal
|
11
|
+
monetize :tax
|
12
|
+
monetize :total
|
13
|
+
|
14
|
+
validates_numericality_of :quantity, allow_blank: true, minimum: 1
|
15
|
+
validates_presence_of :quantity
|
16
|
+
|
17
|
+
def decorated_product
|
18
|
+
product.respond_to?(:decorator) ? product.decorator.decorate(product) : product
|
19
|
+
end
|
20
|
+
|
21
|
+
def name
|
22
|
+
decorated_product.respond_to?(:name) ? decorated_product.name : product.to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
def quantity_and_name
|
26
|
+
"#{quantity} x #{name.pluralize(quantity)}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def subtotal
|
30
|
+
read_attribute(:subtotal) || price * quantity
|
31
|
+
end
|
32
|
+
|
33
|
+
def total
|
34
|
+
read_attribute(:total) || subtotal + calculate_tax
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
def calculate_tax
|
39
|
+
self.tax = 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def calculate_total
|
43
|
+
self.subtotal = price * quantity
|
44
|
+
self.total = subtotal + calculate_tax
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Bodega
|
2
|
+
module Product
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
extend Bodega::Monetize
|
6
|
+
|
7
|
+
has_many :order_products, as: :product, class_name: 'Bodega::OrderProduct'
|
8
|
+
has_many :orders, through: :order_products
|
9
|
+
|
10
|
+
monetize :price
|
11
|
+
|
12
|
+
scope :for_sale, lambda {
|
13
|
+
where('for_sale IS TRUE OR ((for_sale_at >= :today OR for_sale_at IS NULL) AND (not_for_sale_at <= :today OR not_for_sale_at IS NULL))', today: Date.today)
|
14
|
+
}
|
15
|
+
|
16
|
+
# TODO: Get this to use a regular JOIN
|
17
|
+
scope :popular, joins(%(LEFT JOIN "bodega_order_products" ON "bodega_order_products"."product_id" = "#{table_name}"."id" AND "bodega_order_products"."product_type" = '#{name}')).order('SUM(bodega_order_products.quantity) DESC').group("#{table_name}.id")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def in_stock?
|
22
|
+
if keep_stock?
|
23
|
+
number_in_stock > 0
|
24
|
+
else
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def max_for_sale
|
30
|
+
keep_stock? ? number_in_stock : 1000
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<%= form_for(current_order, url: root_path) do |form| %>
|
2
|
+
<table id="bodega-cart">
|
3
|
+
<thead>
|
4
|
+
<tr><th class="product-name" colspan="2"><%= Bodega.config.product_name.titleize %></th><th class="price">Price</th><th class="total">Total</th></tr>
|
5
|
+
</thead>
|
6
|
+
<tbody>
|
7
|
+
<% current_order.order_products.each do |order_product| -%>
|
8
|
+
<tr>
|
9
|
+
<td class="quantity-field">
|
10
|
+
<%= number_field_tag 'products[][quantity]', order_product.quantity, class: 'quantity', max: order_product.product.max_for_sale, min: 1 %>
|
11
|
+
</td>
|
12
|
+
<td class="product-name">
|
13
|
+
<%= order_product.name %>
|
14
|
+
<%= hidden_field_tag 'products[][type]', order_product.product_type %>
|
15
|
+
<%= hidden_field_tag 'products[][id]', order_product.product_id %>
|
16
|
+
</td>
|
17
|
+
<td class="price">
|
18
|
+
$<%= order_product.price %>
|
19
|
+
</td>
|
20
|
+
<td class="subtotal">
|
21
|
+
$<%= order_product.subtotal %>
|
22
|
+
</td>
|
23
|
+
</tr>
|
24
|
+
<% end -%>
|
25
|
+
</tbody>
|
26
|
+
</table>
|
27
|
+
<%= button_tag 'Update Cart', name: :update, value: 1 %>
|
28
|
+
<%= button_tag 'Checkout', name: :checkout, value: 1 %>
|
29
|
+
<% end =%>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= @order.inspect %>
|
data/bodega.gemspec
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "bodega"
|
8
|
+
s.version = "0.2.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Flip Sasser"]
|
12
|
+
s.date = "2012-12-26"
|
13
|
+
s.description = "Bodega adds checkout logic to any model in your app!"
|
14
|
+
s.email = "flip@x451.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.md"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".rspec",
|
20
|
+
".rvmrc",
|
21
|
+
"Gemfile",
|
22
|
+
"Gemfile.lock",
|
23
|
+
"MIT-LICENSE",
|
24
|
+
"README.md",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"app/assets/images/bodega/.gitkeep",
|
28
|
+
"app/assets/javascripts/bodega/application.js",
|
29
|
+
"app/assets/stylesheets/bodega/application.css",
|
30
|
+
"app/controllers/bodega/application_controller.rb",
|
31
|
+
"app/controllers/bodega/orders_controller.rb",
|
32
|
+
"app/helpers/bodega/application_helper.rb",
|
33
|
+
"app/helpers/bodega/cart_helper.rb",
|
34
|
+
"app/models/bodega/order.rb",
|
35
|
+
"app/models/bodega/order_product.rb",
|
36
|
+
"app/models/bodega/product.rb",
|
37
|
+
"app/views/bodega/orders/new.html.erb",
|
38
|
+
"app/views/bodega/orders/show.html.erb",
|
39
|
+
"bodega.gemspec",
|
40
|
+
"config/routes.rb",
|
41
|
+
"db/migrate/20121111170337_create_bodega_orders.rb",
|
42
|
+
"db/migrate/20121111170420_create_bodega_order_products.rb",
|
43
|
+
"lib/bodega.rb",
|
44
|
+
"lib/bodega/engine.rb",
|
45
|
+
"lib/bodega/monetize.rb",
|
46
|
+
"lib/bodega/payment_method.rb",
|
47
|
+
"lib/bodega/payment_method/base.rb",
|
48
|
+
"lib/bodega/payment_method/paypal.rb",
|
49
|
+
"lib/bodega/version.rb",
|
50
|
+
"lib/generators/bodega/install/install_generator.rb",
|
51
|
+
"lib/generators/bodega/product/USAGE",
|
52
|
+
"lib/generators/bodega/product/product_generator.rb",
|
53
|
+
"lib/generators/bodega/product/templates/migration.rb",
|
54
|
+
"lib/generators/bodega/productize/USAGE",
|
55
|
+
"lib/generators/bodega/productize/productize_generator.rb",
|
56
|
+
"lib/generators/bodega/productize/templates/migration.rb",
|
57
|
+
"lib/tasks/bodega_tasks.rake",
|
58
|
+
"script/rails"
|
59
|
+
]
|
60
|
+
s.homepage = "http://github.com/flipsasser/bodega"
|
61
|
+
s.licenses = ["MIT"]
|
62
|
+
s.require_paths = ["lib"]
|
63
|
+
s.rubygems_version = "1.8.24"
|
64
|
+
s.summary = "Bodega adds checkout logic to any model in your app!"
|
65
|
+
|
66
|
+
if s.respond_to? :specification_version then
|
67
|
+
s.specification_version = 3
|
68
|
+
|
69
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
70
|
+
s.add_runtime_dependency(%q<configurator2>, [">= 0.1.1"])
|
71
|
+
s.add_development_dependency(%q<jeweler>, ["= 1.8.4"])
|
72
|
+
s.add_development_dependency(%q<pry>, [">= 0"])
|
73
|
+
else
|
74
|
+
s.add_dependency(%q<configurator2>, [">= 0.1.1"])
|
75
|
+
s.add_dependency(%q<jeweler>, ["= 1.8.4"])
|
76
|
+
s.add_dependency(%q<pry>, [">= 0"])
|
77
|
+
end
|
78
|
+
else
|
79
|
+
s.add_dependency(%q<configurator2>, [">= 0.1.1"])
|
80
|
+
s.add_dependency(%q<jeweler>, ["= 1.8.4"])
|
81
|
+
s.add_dependency(%q<pry>, [">= 0"])
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
data/config/routes.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Bodega::Engine.routes.draw do
|
2
|
+
# Building orders
|
3
|
+
get '', as: :root, to: 'orders#new'
|
4
|
+
post '', to: 'orders#create'
|
5
|
+
|
6
|
+
# Add products to an order
|
7
|
+
post :add, to: 'orders#add'
|
8
|
+
|
9
|
+
# Processing orders
|
10
|
+
get :complete, to: 'orders#complete'
|
11
|
+
post :complete, to: 'orders#complete'
|
12
|
+
|
13
|
+
# Existing orders
|
14
|
+
get ':id', as: :order, to: 'orders#show'
|
15
|
+
post ':id', to: 'orders#update'
|
16
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateBodegaOrders < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :bodega_orders do |t|
|
4
|
+
t.belongs_to :customer, polymorphic: true
|
5
|
+
t.string :identifier, limit: 20
|
6
|
+
t.string :payment_id
|
7
|
+
t.integer :subtotal_in_cents
|
8
|
+
t.integer :tax_in_cents
|
9
|
+
t.integer :total_in_cents
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateBodegaOrderProducts < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :bodega_order_products do |t|
|
4
|
+
t.belongs_to :order
|
5
|
+
t.belongs_to :product, polymorphic: true
|
6
|
+
t.integer :quantity
|
7
|
+
t.integer :price_in_cents
|
8
|
+
t.integer :subtotal_in_cents
|
9
|
+
t.integer :tax_in_cents
|
10
|
+
t.integer :total_in_cents
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/bodega.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'bodega/engine'
|
2
|
+
require 'configurator'
|
3
|
+
|
4
|
+
module Bodega
|
5
|
+
autoload :PaymentMethod, 'bodega/payment_method'
|
6
|
+
|
7
|
+
extend Configurator
|
8
|
+
option :customer_method, :current_user
|
9
|
+
option :product_name, 'product'
|
10
|
+
# Auto-detect payment method. If a user has the Paypal gem installed,
|
11
|
+
# it'll use that. If a user has the Plinq gem installed, it'll use that.
|
12
|
+
# Otherwise, it'll be all, "HEY I NEED A PAYMENT METHOD" when checkout
|
13
|
+
# starts.
|
14
|
+
option :payment_method, lambda {
|
15
|
+
defined?(::Plinq) ? :plinq : defined?(::Paypal) ? :paypal : raise("No payment method detected. Please set one using `Bodega.config.payment_method=`")
|
16
|
+
}
|
17
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Bodega
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace Bodega
|
4
|
+
|
5
|
+
initializer "bodega.hookses" do
|
6
|
+
ActiveSupport.on_load :action_controller do
|
7
|
+
#require 'bodega/action_controller'
|
8
|
+
include Bodega::CartHelper
|
9
|
+
end
|
10
|
+
|
11
|
+
ActiveSupport.on_load :active_record do
|
12
|
+
require 'bodega/monetize'
|
13
|
+
end
|
14
|
+
|
15
|
+
ActiveSupport.on_load :paypal_express do
|
16
|
+
raise 'w0tf'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Bodega
|
2
|
+
module Monetize
|
3
|
+
def monetize(attribute)
|
4
|
+
class_eval <<-monetize
|
5
|
+
def #{attribute}=(value)
|
6
|
+
self.#{attribute}_in_cents = (value.to_f * 100).to_i
|
7
|
+
end
|
8
|
+
|
9
|
+
def #{attribute}
|
10
|
+
(#{attribute}_in_cents || 0) / 100.0
|
11
|
+
end
|
12
|
+
monetize
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Bodega
|
2
|
+
module PaymentMethod
|
3
|
+
autoload :Paypal, 'bodega/payment_method/paypal'
|
4
|
+
autoload :Plinq, 'bodega/payment_method/plinq'
|
5
|
+
|
6
|
+
def payment_method
|
7
|
+
@payment_method ||= "Bodega::PaymentMethod::#{Bodega.config.payment_method.to_s.classify}".constantize.new(current_order, params)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Bodega
|
2
|
+
module PaymentMethod
|
3
|
+
class Base
|
4
|
+
class << self
|
5
|
+
def options(*new_options)
|
6
|
+
option_namespace = self.name.split('::').pop.underscore
|
7
|
+
Bodega.class_eval do
|
8
|
+
option option_namespace do
|
9
|
+
options(*new_options.flatten)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :options, :order
|
16
|
+
|
17
|
+
def checkout_url(success_url, cancel_url)
|
18
|
+
raise "Implement #{self.class.name}#checkout_url"
|
19
|
+
end
|
20
|
+
|
21
|
+
def complete!
|
22
|
+
raise "Implement #{self.class.name}#complete!"
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(order, options)
|
26
|
+
self.order = order
|
27
|
+
self.options = options
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'bodega/payment_method/base'
|
2
|
+
|
3
|
+
module Bodega
|
4
|
+
module PaymentMethod
|
5
|
+
class Paypal < Base
|
6
|
+
options :username, :password, :signature
|
7
|
+
|
8
|
+
def checkout_url(success_url, cancel_url)
|
9
|
+
response = client.setup(request, success_url, cancel_url)
|
10
|
+
response.redirect_uri
|
11
|
+
end
|
12
|
+
|
13
|
+
def complete!
|
14
|
+
response = client.checkout!(
|
15
|
+
options[:token],
|
16
|
+
options[:PayerID],
|
17
|
+
request
|
18
|
+
)
|
19
|
+
response.payment_info.last.transaction_id
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
def client
|
24
|
+
@client ||= ::Paypal::Express::Request.new(
|
25
|
+
username: Bodega.config.paypal.username,
|
26
|
+
password: Bodega.config.paypal.password,
|
27
|
+
signature: Bodega.config.paypal.signature
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def request
|
32
|
+
@request ||= ::Paypal::Payment::Request.new(
|
33
|
+
amount: order.subtotal,
|
34
|
+
description: order.order_products.map(&:quantity_and_name).to_sentence
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
File without changes
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rails/generators/migration'
|
2
|
+
require 'rails/generators/active_record/migration'
|
3
|
+
|
4
|
+
module Bodega
|
5
|
+
module Generators
|
6
|
+
class ProductGenerator < Rails::Generators::Base
|
7
|
+
argument :product_name, type: :string
|
8
|
+
|
9
|
+
include Rails::Generators::Migration
|
10
|
+
extend ActiveRecord::Generators::Migration
|
11
|
+
|
12
|
+
source_root File.expand_path('../templates', __FILE__)
|
13
|
+
|
14
|
+
def copy_migration
|
15
|
+
migration_template("migration.rb", "db/migrate/bodegaize_#{product_name.tableize}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Create<%= product_name.classify.pluralize %> < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :<%= product_name.tableize %> do
|
4
|
+
t.integer :price_in_cents
|
5
|
+
t.boolean :for_sale, default: true
|
6
|
+
t.boolean :keep_stock, default: false
|
7
|
+
t.integer :number_in_stock
|
8
|
+
t.datetime :for_sale_at
|
9
|
+
t.datetime :not_for_sale_at
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rails/generators/migration'
|
2
|
+
require 'rails/generators/active_record/migration'
|
3
|
+
|
4
|
+
module Bodega
|
5
|
+
module Generators
|
6
|
+
class ProductizeGenerator < Rails::Generators::Base
|
7
|
+
argument :product_name, type: :string
|
8
|
+
|
9
|
+
include Rails::Generators::Migration
|
10
|
+
extend ActiveRecord::Generators::Migration
|
11
|
+
|
12
|
+
source_root File.expand_path('../templates', __FILE__)
|
13
|
+
|
14
|
+
def copy_migration
|
15
|
+
migration_template("migration.rb", "db/migrate/bodegaize_#{product_name.tableize}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class Bodegaize<%= product_name.classify.pluralize %> < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
add_column :<%= product_name.tableize %>, :price_in_cents, :integer
|
4
|
+
add_column :<%= product_name.tableize %>, :for_sale, :boolean, default: true
|
5
|
+
add_column :<%= product_name.tableize %>, :keep_stock, :boolean, default: false
|
6
|
+
add_column :<%= product_name.tableize %>, :number_in_stock, :integer
|
7
|
+
add_column :<%= product_name.tableize %>, :for_sale_at, :datetime
|
8
|
+
add_column :<%= product_name.tableize %>, :not_for_sale_at, :datetime
|
9
|
+
end
|
10
|
+
end
|
data/script/rails
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
|
3
|
+
|
4
|
+
ENGINE_ROOT = File.expand_path('../..', __FILE__)
|
5
|
+
ENGINE_PATH = File.expand_path('../../lib/bodega/engine', __FILE__)
|
6
|
+
|
7
|
+
require 'rails/all'
|
8
|
+
require 'rails/engine/commands'
|
metadata
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bodega
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Flip Sasser
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: configurator2
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.1.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.1.1
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: jeweler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - '='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.8.4
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - '='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.8.4
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: pry
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Bodega adds checkout logic to any model in your app!
|
63
|
+
email: flip@x451.com
|
64
|
+
executables: []
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files:
|
67
|
+
- README.md
|
68
|
+
files:
|
69
|
+
- .rspec
|
70
|
+
- .rvmrc
|
71
|
+
- Gemfile
|
72
|
+
- Gemfile.lock
|
73
|
+
- MIT-LICENSE
|
74
|
+
- README.md
|
75
|
+
- Rakefile
|
76
|
+
- VERSION
|
77
|
+
- app/assets/images/bodega/.gitkeep
|
78
|
+
- app/assets/javascripts/bodega/application.js
|
79
|
+
- app/assets/stylesheets/bodega/application.css
|
80
|
+
- app/controllers/bodega/application_controller.rb
|
81
|
+
- app/controllers/bodega/orders_controller.rb
|
82
|
+
- app/helpers/bodega/application_helper.rb
|
83
|
+
- app/helpers/bodega/cart_helper.rb
|
84
|
+
- app/models/bodega/order.rb
|
85
|
+
- app/models/bodega/order_product.rb
|
86
|
+
- app/models/bodega/product.rb
|
87
|
+
- app/views/bodega/orders/new.html.erb
|
88
|
+
- app/views/bodega/orders/show.html.erb
|
89
|
+
- bodega.gemspec
|
90
|
+
- config/routes.rb
|
91
|
+
- db/migrate/20121111170337_create_bodega_orders.rb
|
92
|
+
- db/migrate/20121111170420_create_bodega_order_products.rb
|
93
|
+
- lib/bodega.rb
|
94
|
+
- lib/bodega/engine.rb
|
95
|
+
- lib/bodega/monetize.rb
|
96
|
+
- lib/bodega/payment_method.rb
|
97
|
+
- lib/bodega/payment_method/base.rb
|
98
|
+
- lib/bodega/payment_method/paypal.rb
|
99
|
+
- lib/bodega/version.rb
|
100
|
+
- lib/generators/bodega/install/install_generator.rb
|
101
|
+
- lib/generators/bodega/product/USAGE
|
102
|
+
- lib/generators/bodega/product/product_generator.rb
|
103
|
+
- lib/generators/bodega/product/templates/migration.rb
|
104
|
+
- lib/generators/bodega/productize/USAGE
|
105
|
+
- lib/generators/bodega/productize/productize_generator.rb
|
106
|
+
- lib/generators/bodega/productize/templates/migration.rb
|
107
|
+
- lib/tasks/bodega_tasks.rake
|
108
|
+
- script/rails
|
109
|
+
homepage: http://github.com/flipsasser/bodega
|
110
|
+
licenses:
|
111
|
+
- MIT
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
segments:
|
123
|
+
- 0
|
124
|
+
hash: 640289259444713481
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
|
+
none: false
|
127
|
+
requirements:
|
128
|
+
- - ! '>='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
requirements: []
|
132
|
+
rubyforge_project:
|
133
|
+
rubygems_version: 1.8.24
|
134
|
+
signing_key:
|
135
|
+
specification_version: 3
|
136
|
+
summary: Bodega adds checkout logic to any model in your app!
|
137
|
+
test_files: []
|