comable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +46 -0
  4. data/Rakefile +28 -0
  5. data/app/assets/javascripts/comable/application.js +13 -0
  6. data/app/assets/stylesheets/comable/application.css +13 -0
  7. data/app/controllers/comable/application_controller.rb +5 -0
  8. data/app/controllers/comable/carts_controller.rb +29 -0
  9. data/app/controllers/comable/orders_controller.rb +108 -0
  10. data/app/controllers/comable/products_controller.rb +11 -0
  11. data/app/helpers/comable/application_helper.rb +18 -0
  12. data/app/helpers/comable/products_helper.rb +55 -0
  13. data/app/models/comable/cart_item.rb +36 -0
  14. data/app/models/comable/customer.rb +115 -0
  15. data/app/models/comable/order.rb +29 -0
  16. data/app/models/comable/order_delivery.rb +12 -0
  17. data/app/models/comable/order_detail.rb +13 -0
  18. data/app/models/comable/product.rb +32 -0
  19. data/app/models/comable/stock.rb +31 -0
  20. data/app/views/comable/carts/show.slim +23 -0
  21. data/app/views/comable/orders/confirm.slim +25 -0
  22. data/app/views/comable/orders/create.slim +5 -0
  23. data/app/views/comable/orders/delivery.slim +8 -0
  24. data/app/views/comable/orders/new.slim +4 -0
  25. data/app/views/comable/orders/orderer.slim +7 -0
  26. data/app/views/comable/products/index.slim +11 -0
  27. data/app/views/comable/products/show.slim +20 -0
  28. data/app/views/layouts/comable/application.slim +11 -0
  29. data/config/locales/ja.yml +10 -0
  30. data/config/routes.rb +21 -0
  31. data/db/migrate/20131214194807_create_comable_products.rb +14 -0
  32. data/db/migrate/20140120024159_create_comable_cart_items.rb +12 -0
  33. data/db/migrate/20140120032559_create_comable_customers.rb +8 -0
  34. data/db/migrate/20140502060116_create_comable_stocks.rb +14 -0
  35. data/db/migrate/20140723175431_create_comable_orders.rb +13 -0
  36. data/db/migrate/20140723175624_create_comable_order_deliveries.rb +9 -0
  37. data/db/migrate/20140723175810_create_comable_order_details.rb +10 -0
  38. data/lib/comable.rb +8 -0
  39. data/lib/comable/cash_register.rb +90 -0
  40. data/lib/comable/decoratable.rb +10 -0
  41. data/lib/comable/engine.rb +8 -0
  42. data/lib/comable/migration.rb +16 -0
  43. data/lib/comable/version.rb +3 -0
  44. data/lib/tasks/comable_tasks.rake +4 -0
  45. metadata +284 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cdfd44cf66aa3b1b326e80bef01bdde6f1c0447e
4
+ data.tar.gz: ef5123467b8427a8ca186b342ae58449fe0b62e0
5
+ SHA512:
6
+ metadata.gz: 91ecb5a8436788eab87d6443b4238cd5473ef327c1a77eb8bc40990b589f0c3c3ff4bca20b96a6895d092ffb6c9ebc2e60c45a3d7681a222e4ea99e0686282d2
7
+ data.tar.gz: 1b987222eab78a0c8f796b71fdc44cd6327b3fde26f7dec98e249e5162afa53b2e1f33e80a0c1bd6d561b24105d713bcdcea8dd2362b3d7e0e6323af377700c9
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 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,46 @@
1
+ # Comable
2
+
3
+ [![Build Status](https://secure.travis-ci.org/hyoshida/comable.png)](http://travis-ci.org/hyoshida/comable)
4
+ [![Code Climate](https://codeclimate.com/github/hyoshida/comable.png)](https://codeclimate.com/github/hyoshida/comable)
5
+ [![Coverage Status](https://coveralls.io/repos/hyoshida/comable/badge.png)](https://coveralls.io/r/hyoshida/comable)
6
+ [![Dependency Status](https://gemnasium.com/hyoshida/comable.svg)](https://gemnasium.com/hyoshida/comable)
7
+
8
+ Comable provides a simple way to add e-commerce features to your Ruby on Rails application.
9
+
10
+ ## Installation
11
+
12
+ 1. Add comable in the `Gemfile`:
13
+
14
+ ```ruby
15
+ gem 'comable'
16
+ ```
17
+
18
+ 2. Download and install by running:
19
+
20
+ ```bash
21
+ bundle install
22
+ ```
23
+
24
+ 3. Get gem migrations:
25
+
26
+ ```bash
27
+ bundle exec rake comable:install:migrations
28
+ ```
29
+
30
+ ## Requirements
31
+
32
+ * Ruby on Rails 3.2, 4.1
33
+ * Ruby 2.1
34
+
35
+ And `strong_parameters`, `everywhere` gems are required only for Rails 3.
36
+
37
+ ## Development
38
+
39
+ To set up a development environment, simply do:
40
+
41
+ ```bash
42
+ bundle install
43
+ bundle exec rake db:create
44
+ bundle exec rake db:migrate
45
+ bundle exec rake # run the test suite
46
+ ```
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Comable'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('lib/**/*.rb')
14
+ end
15
+
16
+ APP_RAKEFILE = File.expand_path('../spec/dummy/Rakefile', __FILE__)
17
+ load 'rails/tasks/engine.rake'
18
+
19
+ Bundler::GemHelper.install_tasks
20
+
21
+ # from https://github.com/rspec/rspec-rails/issues/936
22
+ task 'test:prepare'
23
+
24
+ task :rubocop do
25
+ exec 'rubocop'
26
+ end
27
+
28
+ task default: ['app:spec', 'rubocop']
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -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,5 @@
1
+ module Comable
2
+ class ApplicationController < ActionController::Base
3
+ include Comable::ApplicationHelper
4
+ end
5
+ end
@@ -0,0 +1,29 @@
1
+ module Comable
2
+ class CartsController < ApplicationController
3
+ def show
4
+ end
5
+
6
+ def add
7
+ product = Comable::Product.where(id: params[:product_id]).first
8
+ return redirect_by_product_not_found unless product
9
+
10
+ if product.sku?
11
+ stock = product.stocks.where(id: params[:stock_id]).first
12
+ return redirect_by_product_not_found unless stock
13
+ end
14
+
15
+ # TODO: 在庫確認
16
+ current_customer.add_cart_item(stock || product)
17
+
18
+ flash[:notice] = I18n.t('comable.carts.add_product')
19
+ redirect_to cart_path
20
+ end
21
+
22
+ private
23
+
24
+ def redirect_by_product_not_found
25
+ flash[:error] = I18n.t('comable.carts.product_not_found')
26
+ redirect_to :back
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,108 @@
1
+ module Comable
2
+ class OrdersController < ApplicationController
3
+ before_filter :load_order
4
+ before_filter :verify
5
+ before_filter :redirect_for_logged_in_customer, only: [:new, :orderer]
6
+ after_filter :save_order, except: :create
7
+
8
+ def new
9
+ end
10
+
11
+ def orderer
12
+ case request.method_symbol
13
+ when :post
14
+ redirect_to comable.delivery_order_path
15
+ end
16
+ end
17
+
18
+ def delivery
19
+ case request.method_symbol
20
+ when :post
21
+ redirect_to comable.confirm_order_path
22
+ when :get
23
+ @order.order_deliveries.build if @order.order_deliveries.empty?
24
+ end
25
+ end
26
+
27
+ def confirm
28
+ @order = current_customer.preorder(build_order_nested_attributes)
29
+ end
30
+
31
+ def create
32
+ @order = current_customer.order(build_order_nested_attributes)
33
+ if @order.valid?
34
+ flash[:notice] = I18n.t('comable.orders.success')
35
+ reset_session
36
+ else
37
+ flash[:alert] = I18n.t('comable.orders.failure')
38
+ redirect_to comable.confirm_order_path
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def reset_session
45
+ session.delete('comable.order')
46
+ end
47
+
48
+ def verify
49
+ return if current_customer.cart.any?
50
+ flash[:alert] = I18n.t('comable.carts.empty')
51
+ redirect_to comable.cart_path
52
+ end
53
+
54
+ def load_order
55
+ order_attributes = JSON.parse(session['comable.order'] || '{}')
56
+ order_attributes.update(order_params) if order_params
57
+ @order = Comable::Order.new(order_attributes)
58
+ end
59
+
60
+ def save_order
61
+ session['comable.order'] = build_order_nested_attributes.to_json
62
+ end
63
+
64
+ def build_order_nested_attributes
65
+ @order.attributes.merge(
66
+ order_deliveries_attributes: build_order_delivery_nested_attributes
67
+ )
68
+ end
69
+
70
+ def build_order_delivery_nested_attributes
71
+ @order.order_deliveries.map do |order_delivery|
72
+ order_delivery.attributes.merge(
73
+ order_details_attributes: order_delivery.order_details.map(&:attributes)
74
+ )
75
+ end
76
+ end
77
+
78
+ def order_params
79
+ return unless params[:order]
80
+ case action_name.to_sym
81
+ when :orderer
82
+ order_params_for_orderer
83
+ when :delivery
84
+ order_params_for_delivery
85
+ end
86
+ end
87
+
88
+ def order_params_for_orderer
89
+ params.require(:order).permit(
90
+ :family_name,
91
+ :first_name
92
+ )
93
+ end
94
+
95
+ def order_params_for_delivery
96
+ params.require(:order).permit(
97
+ order_deliveries_attributes: [
98
+ :family_name,
99
+ :first_name
100
+ ]
101
+ )
102
+ end
103
+
104
+ def redirect_for_logged_in_customer
105
+ return redirect_to delivery_order_path if current_customer.logged_in?
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,11 @@
1
+ module Comable
2
+ class ProductsController < ApplicationController
3
+ def index
4
+ @products = Comable::Product.all
5
+ end
6
+
7
+ def show
8
+ @product = Comable::Product.find(params[:id])
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ module Comable
2
+ module ApplicationHelper
3
+ def current_customer
4
+ @current_customer || load_customer
5
+ end
6
+
7
+ private
8
+
9
+ def load_customer
10
+ @current_customer = logged_in_customer
11
+ @current_customer ||= Comable::Customer.new(cookies)
12
+ end
13
+
14
+ def logged_in_customer
15
+ # Please override this method for logged in customer
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,55 @@
1
+ module Comable
2
+ module ProductsHelper
3
+ def sku_table(product, options = nil)
4
+ stocks = product.stocks
5
+ content_tag(:table, nil, options) do
6
+ html = ''
7
+ html << build_sku_v_table_header(product, stocks)
8
+ html << build_sku_table_rows(product, stocks)
9
+ html.html_safe
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def build_sku_v_table_header(product, stocks)
16
+ sku_item_name = product.sku_h_item_name
17
+ sku_item_name += '/' + product.sku_v_item_name if product.sku_v?
18
+
19
+ html = ''
20
+ html << content_tag(:th, sku_item_name)
21
+ stocks.group_by(&:sku_h_choice_name).keys.each do |sku_h_choice_name|
22
+ next if sku_h_choice_name.blank?
23
+ html << content_tag(:th, sku_h_choice_name)
24
+ end
25
+ html.html_safe
26
+ end
27
+
28
+ def build_sku_table_rows(product, stocks)
29
+ return content_tag(:tr, build_sku_table_row(stocks)) unless product.sku_v?
30
+
31
+ html = ''
32
+ stocks.group_by(&:sku_v_choice_name).each_pair do |sku_v_choice_name, sku_v_stocks|
33
+ next if sku_v_choice_name.blank?
34
+ html << content_tag(:tr, build_sku_table_row(sku_v_stocks, sku_v_choice_name))
35
+ end
36
+ html.html_safe
37
+ end
38
+
39
+ def build_sku_table_row(stocks, sku_v_choice_name = nil)
40
+ html = ''
41
+ html << content_tag(:th, sku_v_choice_name)
42
+ html << stocks.map { |stock| content_tag(:td, build_sku_product_label(stock)) }.join
43
+ html.html_safe
44
+ end
45
+
46
+ def build_sku_product_label(stock)
47
+ content_tag(:label) do
48
+ html = ''
49
+ html << radio_button_tag(:stock_id, stock.id, false, disabled: stock.soldout?)
50
+ html << stock.code
51
+ html.html_safe
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,36 @@
1
+ module Comable
2
+ class CartItem < ActiveRecord::Base
3
+ utusemi!
4
+
5
+ belongs_to :customer, class_name: Comable::Customer.name, foreign_key: Comable::Customer.table_name.singularize.foreign_key
6
+ belongs_to :stock, class_name: Comable::Stock.name, foreign_key: Comable::Stock.table_name.singularize.foreign_key
7
+
8
+ with_options if: :customer do |customer|
9
+ customer.validates Comable::Customer.table_name.singularize.foreign_key, uniqueness: { scope: [Comable::Customer.table_name.singularize.foreign_key, Comable::Stock.table_name.singularize.foreign_key] }
10
+ customer.validates Comable::Customer.table_name.singularize.foreign_key, presence: true
11
+ end
12
+
13
+ with_options if: :guest_token do |guest|
14
+ guest.validates :guest_token, uniqueness: { scope: [:guest_token, Comable::Stock.table_name.singularize.foreign_key] }
15
+ guest.validates :guest_token, presence: true
16
+ end
17
+
18
+ delegate :product, to: :stock
19
+
20
+ before_create :generate_guest_token
21
+
22
+ def price
23
+ stock.price * quantity
24
+ end
25
+
26
+ private
27
+
28
+ def generate_guest_token
29
+ return if send(Comable::Customer.table_name.singularize.foreign_key)
30
+ self.guest_token ||= loop do
31
+ random_token = SecureRandom.urlsafe_base64(nil, false)
32
+ break random_token unless self.class.exists?(guest_token: random_token)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,115 @@
1
+ module Comable
2
+ class Customer < ActiveRecord::Base
3
+ include Decoratable
4
+
5
+ has_many :comable_orders, class_name: Comable::Order.name, foreign_key: table_name.singularize.foreign_key
6
+ alias_method :orders, :comable_orders
7
+
8
+ def initialize(*args)
9
+ obj = args.first
10
+ case obj.class.name
11
+ when /Cookies/
12
+ @cookies = obj
13
+ super()
14
+ else
15
+ super
16
+ end
17
+ end
18
+
19
+ def logged_in?
20
+ !new_record?
21
+ end
22
+
23
+ def not_logged_in?
24
+ !logged_in?
25
+ end
26
+
27
+ def add_cart_item(obj)
28
+ case obj
29
+ when Comable::Product
30
+ add_stock_to_cart(obj.stocks.first)
31
+ when Comable::Stock
32
+ add_stock_to_cart(obj)
33
+ when Array
34
+ obj.map { |item| add_cart_item(item) }
35
+ else
36
+ fail
37
+ end
38
+ end
39
+
40
+ def remove_cart_item(obj)
41
+ case obj
42
+ when Comable::Stock
43
+ remove_stock_from_cart(obj)
44
+ else
45
+ fail
46
+ end
47
+ end
48
+
49
+ def reset_cart
50
+ cart_items.destroy_all
51
+ end
52
+
53
+ def cart_items
54
+ Comable::CartItem.where(
55
+ Comable::Customer.table_name.singularize.foreign_key => id,
56
+ :guest_token => current_guest_token
57
+ )
58
+ end
59
+
60
+ def cart
61
+ Cart.new(cart_items)
62
+ end
63
+
64
+ class Cart < Array
65
+ def price
66
+ cart_item_ids = map(&:id)
67
+ Comable::CartItem.includes(stock: :product).where(id: cart_item_ids).to_a.sum(&:price)
68
+ end
69
+ end
70
+
71
+ def preorder(order_params = {})
72
+ Comable::CashRegister.new(customer: self, order_attributes: order_params).build_order
73
+ end
74
+
75
+ def order(order_params = {})
76
+ Comable::CashRegister.new(customer: self, order_attributes: order_params).create_order
77
+ end
78
+
79
+ private
80
+
81
+ def current_guest_token
82
+ return if logged_in?
83
+ @cookies.signed[:guest_token]
84
+ end
85
+
86
+ def add_stock_to_cart(stock)
87
+ fail I18n.t('comable.carts.product_not_stocked') if stock.soldout?
88
+
89
+ cart_items = find_cart_items_by(stock)
90
+ if cart_items.any?
91
+ cart_item = cart_items.first
92
+ cart_item.increment!(:quantity)
93
+ else
94
+ cart_item = cart_items.create
95
+ @cookies.permanent.signed[:guest_token] = cart_item.guest_token if not_logged_in?
96
+ end
97
+ end
98
+
99
+ def remove_stock_from_cart(stock)
100
+ cart_item = find_cart_items_by(stock).first
101
+ return false unless cart_item
102
+
103
+ if cart_item.quantity.pred.nonzero?
104
+ cart_item.decrement!(:quantity)
105
+ else
106
+ cart_item.destroy
107
+ end
108
+ end
109
+
110
+ def find_cart_items_by(stock)
111
+ fail I18n.t('comable.carts.product_not_found') unless stock.is_a?(Comable::Stock)
112
+ cart_items.where(Comable::Stock.table_name.singularize.foreign_key => stock.id)
113
+ end
114
+ end
115
+ end