shopping_cart 0.1.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +100 -0
- data/Rakefile +37 -0
- data/app/assets/config/shopping_cart_manifest.js +2 -0
- data/app/assets/images/shopping_cart/back.png +0 -0
- data/app/assets/images/shopping_cart/delete.png +0 -0
- data/app/assets/images/shopping_cart/empty_cart.png +0 -0
- data/app/assets/images/shopping_cart/refresh.png +0 -0
- data/app/assets/javascripts/shopping_cart/application.js +4 -0
- data/app/assets/javascripts/shopping_cart/order_items.js.coffee +4 -0
- data/app/assets/stylesheets/shopping_cart/application.scss +3 -0
- data/app/assets/stylesheets/shopping_cart/order_items.scss +7 -0
- data/app/controllers/shopping_cart/application_controller.rb +23 -0
- data/app/controllers/shopping_cart/checkout_controller.rb +99 -0
- data/app/controllers/shopping_cart/order_items_controller.rb +51 -0
- data/app/helpers/shopping_cart/application_helper.rb +4 -0
- data/app/helpers/shopping_cart/order_items_helper.rb +7 -0
- data/app/helpers/shopping_cart/orders_helper.rb +7 -0
- data/app/jobs/shopping_cart/application_job.rb +4 -0
- data/app/mailers/shopping_cart/application_mailer.rb +6 -0
- data/app/models/ability.rb +8 -0
- data/app/models/shopping_cart/address.rb +7 -0
- data/app/models/shopping_cart/application_record.rb +5 -0
- data/app/models/shopping_cart/credit_card.rb +9 -0
- data/app/models/shopping_cart/discount.rb +5 -0
- data/app/models/shopping_cart/order.rb +47 -0
- data/app/models/shopping_cart/order_item.rb +9 -0
- data/app/models/shopping_cart/shipping.rb +4 -0
- data/app/views/layouts/shopping_cart/application.html.haml +12 -0
- data/app/views/layouts/shopping_cart/checkout.html.haml +30 -0
- data/app/views/shopping_cart/checkout/complete.html.haml +54 -0
- data/app/views/shopping_cart/checkout/index.html.haml +23 -0
- data/app/views/shopping_cart/checkout/payment.html.haml +26 -0
- data/app/views/shopping_cart/checkout/shipping.html.haml +6 -0
- data/app/views/shopping_cart/order_items/index.html.haml +68 -0
- data/config/locales/shopping_cart.en.yml +35 -0
- data/config/routes.rb +16 -0
- data/db/migrate/20160810062129_create_shopping_cart_order_items.rb +11 -0
- data/db/migrate/20160810063613_create_shopping_cart_orders.rb +14 -0
- data/db/migrate/20160811075244_create_shopping_cart_discounts.rb +10 -0
- data/db/migrate/20160815065526_create_shopping_cart_addresses.rb +13 -0
- data/db/migrate/20160816093049_create_shopping_cart_shippings.rb +8 -0
- data/db/migrate/20160817082741_create_shopping_cart_credit_cards.rb +13 -0
- data/lib/generators/shopping_cart/config_generator.rb +24 -0
- data/lib/generators/shopping_cart/controller_generator.rb +47 -0
- data/lib/generators/shopping_cart/translate_generator.rb +43 -0
- data/lib/generators/shopping_cart/views_generator.rb +17 -0
- data/lib/shopping_cart.rb +7 -0
- data/lib/shopping_cart/engine.rb +15 -0
- data/lib/shopping_cart/version.rb +3 -0
- data/lib/tasks/shopping_cart_tasks.rake +4 -0
- metadata +193 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: de878f70210e599591a870b65c1a30fd1fcafcf2
|
|
4
|
+
data.tar.gz: 45991e81778b420259ba56391bab6d86b732924d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4ac5f31a87a6c9bbdb8f198aba8057fc47095286154762564e77b9c81173343c09cda3fd6b7371468e56d17f9a35b79fd73e55507b7ac83664629d8527b12d4e
|
|
7
|
+
data.tar.gz: e8ed59020c130a2afdb007e3954ec604852a410d7bb07292f4665e9343ea4a31bd2d7b708b89d32186c53c64cd5922062ebb83faaecd96646fd04ac6a5a9f79d
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2016 Igor
|
|
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,100 @@
|
|
|
1
|
+
# ShoppingCart
|
|
2
|
+
Simple gem which adds shopping cart and checkout ability to your shop.
|
|
3
|
+
|
|
4
|
+
## Installation
|
|
5
|
+
Add this line to your application's Gemfile:
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
gem 'shopping_cart', github: 'rockandruby/shopping_cart'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
Define root in your routes and run 'rails g shopping_cart:config' to install shopping cart.
|
|
13
|
+
|
|
14
|
+
Ensure you have flash messages in app/views/layouts/application.html.erb.
|
|
15
|
+
For example:
|
|
16
|
+
|
|
17
|
+
<p class="notice"><%= notice %></p>
|
|
18
|
+
<p class="alert"><%= alert %></p>
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
Firstly, gem depends on devise. Thus your app must have current_{model_name} method to perform
|
|
22
|
+
shopping cart functionality. For right now cart is accessible only for signed in users. Devise
|
|
23
|
+
added as a dependency for cart thus you don't need add devise to your gemfile. All you need - to install device.
|
|
24
|
+
|
|
25
|
+
In config/initializers you will find shopping_cart initializer. By default, name of your user model is
|
|
26
|
+
'User' but you can override it by changing ShoppingCart.user_class = 'User' with proper model name in
|
|
27
|
+
string format.
|
|
28
|
+
|
|
29
|
+
By default, you have 4 steps for checkout: address, payment, shipping and complete. Payment and shipping
|
|
30
|
+
are optional. In shopping cart initializer you have ShoppingCart.order_steps = %i(shipping payment) array.
|
|
31
|
+
If you want to delete step, just delete it from array. If you want to delete optional steps at all, just
|
|
32
|
+
comment the array ans you will have only address and complete steps.
|
|
33
|
+
|
|
34
|
+
Notice!!! Order of elements in ShoppingCart.order_steps are equal to steps' order in your app.
|
|
35
|
+
|
|
36
|
+
Notice!!!! If you use shipping step you should create needed shippings like
|
|
37
|
+
ShoppingCart::Shipping.create(title:'Shipping title', price: 123).
|
|
38
|
+
|
|
39
|
+
When you install shopping cart, you retrieve migrations for default steps. You can delete proper migration for
|
|
40
|
+
step you want to skip. Then run rails db:migrate to run migrations.
|
|
41
|
+
|
|
42
|
+
Of course, you can add your own steps. Just put the name of your step to steps' array in initializer and
|
|
43
|
+
restart your app .
|
|
44
|
+
|
|
45
|
+
Run "rails g shopping_cart:controller MySteps 'first_step' 'second_step' " to generate controller,
|
|
46
|
+
proper actions, views and routes for your steps. Don't forget that steps' names should be equal to names in
|
|
47
|
+
your initializer.
|
|
48
|
+
Don't forget that all entities should be in shopping_cart namespace. For instance, to create model for
|
|
49
|
+
your steps you suppose to run shopping_cart/YourModelName.
|
|
50
|
+
|
|
51
|
+
Your step controller will have 3 main methods:
|
|
52
|
+
1) check_step(:your_step) which checks whether user able to proceed
|
|
53
|
+
action and returns true/false.
|
|
54
|
+
2) update_step(:your_step) which allows user to proceed to next step.
|
|
55
|
+
3) redirect_by_step(:your_step) which redirects to next step.
|
|
56
|
+
|
|
57
|
+
Below simple example how it works:
|
|
58
|
+
def your_step
|
|
59
|
+
return redirect_to :back unless check_step(:your_step)
|
|
60
|
+
//do some code
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def add_your_step
|
|
64
|
+
//do some code
|
|
65
|
+
update_step(:your_step)
|
|
66
|
+
redirect_by_step(:your_step)
|
|
67
|
+
end
|
|
68
|
+
So
|
|
69
|
+
|
|
70
|
+
You should add the following association for your user model "has_many :orders,
|
|
71
|
+
:class_name => 'ShoppingCart::Order'".
|
|
72
|
+
|
|
73
|
+
Cart can process any product type quantity, you just need to add the following association to
|
|
74
|
+
your product model(e.g. Book, Magazine etc.)
|
|
75
|
+
"has_many :order_items, :class_name => 'ShoppingCart::OrderItem', as: :productable".
|
|
76
|
+
Also your model must have 'price' attribute.
|
|
77
|
+
|
|
78
|
+
You form should include following fields:
|
|
79
|
+
hidden field "name='model' value='your model class'" e.g. "value='@book.class'".
|
|
80
|
+
text field "name='quantity'" and hidden field "name='id' value='@book.id'". For instance,
|
|
81
|
+
|
|
82
|
+
=form_tag(shopping_cart.order_items_path, class: 'form-inline') do
|
|
83
|
+
=text_field_tag(:quantity, '', class: 'form-control', type: 'number', required: true, min: 1)
|
|
84
|
+
=hidden_field_tag(:id, @book.id)
|
|
85
|
+
=hidden_field_tag(:model, @book.class)
|
|
86
|
+
=submit_tag
|
|
87
|
+
|
|
88
|
+
= link_to('cart', shopping_cart.order_items_path)
|
|
89
|
+
|
|
90
|
+
Submit your form on the following path shopping_cart.order_items_path.
|
|
91
|
+
|
|
92
|
+
You can override views and layout for shopping cart by generating them to your project with
|
|
93
|
+
'rails g shopping_cart:views'.
|
|
94
|
+
|
|
95
|
+
Also, you're able to use discount for your cart. Just create the proper discount, for example:
|
|
96
|
+
ShoppingCart::Discount.create(code: 'verification code', amount: 10), where amount is percentage.
|
|
97
|
+
If you don't need discount ability, delete the proper button from view.
|
|
98
|
+
|
|
99
|
+
I18n:
|
|
100
|
+
Run 'rails g shopping_cart:translate' to generate internationalization file to your app.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
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 = 'ShoppingCart'
|
|
12
|
+
rdoc.options << '--line-numbers'
|
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
|
18
|
+
load 'rails/tasks/engine.rake'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
load 'rails/tasks/statistics.rake'
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
require 'bundler/gem_tasks'
|
|
26
|
+
|
|
27
|
+
require 'rake/testtask'
|
|
28
|
+
|
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
|
30
|
+
t.libs << 'lib'
|
|
31
|
+
t.libs << 'test'
|
|
32
|
+
t.pattern = 'test/**/*_test.rb'
|
|
33
|
+
t.verbose = false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
task default: :test
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module ShoppingCart
|
|
2
|
+
class ApplicationController < ActionController::Base
|
|
3
|
+
protect_from_forgery with: :exception
|
|
4
|
+
before_action "authenticate_#{ShoppingCart.user_class.downcase}!".to_sym
|
|
5
|
+
alias_method :current_user, "current_#{ShoppingCart.user_class.downcase}".to_sym
|
|
6
|
+
|
|
7
|
+
rescue_from CanCan::AccessDenied do |exception|
|
|
8
|
+
redirect_to '/', alert: exception.message
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
protected
|
|
12
|
+
|
|
13
|
+
def get_order
|
|
14
|
+
@user = send("current_#{ShoppingCart.user_class.downcase}")
|
|
15
|
+
@current_order = Order.current_order(@user)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def check_order
|
|
19
|
+
redirect_to root_path unless @current_order && @current_order.order_items_count > 0
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
require_dependency "shopping_cart/application_controller"
|
|
2
|
+
|
|
3
|
+
module ShoppingCart
|
|
4
|
+
class CheckoutController < ApplicationController
|
|
5
|
+
before_action :get_order
|
|
6
|
+
before_action :check_order
|
|
7
|
+
before_action :init_steps
|
|
8
|
+
before_action :init_address, only: [:index, :add_address]
|
|
9
|
+
before_action :init_payment, only: [:payment, :add_payment]
|
|
10
|
+
before_action :check_complete, only: [:complete, :create]
|
|
11
|
+
|
|
12
|
+
layout 'shopping_cart/checkout'
|
|
13
|
+
|
|
14
|
+
def shipping
|
|
15
|
+
redirect_to :back unless check_step(:shipping)
|
|
16
|
+
@shippings = Shipping.all
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def payment
|
|
20
|
+
redirect_to :back unless check_step(:payment)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def create
|
|
24
|
+
@current_order.place_order
|
|
25
|
+
@current_order.update(completed_at: Time.now, total_price: @current_order.total_price)
|
|
26
|
+
flash[:notice] = t('shopping_cart.order_placed')
|
|
27
|
+
redirect_to '/'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def add_address
|
|
31
|
+
return render :index unless @address.update(address_params)
|
|
32
|
+
@current_order.update(step: 0) unless @current_order.step
|
|
33
|
+
redirect_to @steps ? "/shopping_cart/checkout/#{@steps[0]}" : '/shopping_cart/checkout/complete'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def add_shipping
|
|
37
|
+
unless params[:id]
|
|
38
|
+
flash[:alert] = t('shopping_cart.choose_shipping')
|
|
39
|
+
return redirect_to :back
|
|
40
|
+
end
|
|
41
|
+
shipping = Shipping.find(params[:id])
|
|
42
|
+
@current_order.update(shipping: shipping)
|
|
43
|
+
update_step(:shipping)
|
|
44
|
+
redirect_by_step(:shipping)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def add_payment
|
|
48
|
+
return render :payment unless @credit_card.update(payment_params)
|
|
49
|
+
update_step(:payment)
|
|
50
|
+
redirect_by_step(:payment)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def init_steps
|
|
56
|
+
@steps = ShoppingCart.order_steps
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def init_address
|
|
60
|
+
@address = @current_order.address || Address.new(order: @current_order)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def init_payment
|
|
64
|
+
@credit_card = @current_order.credit_card || CreditCard.new(order: @current_order)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def address_params
|
|
68
|
+
params.require(:address).permit(:first_name, :last_name, :city, :phone, :details)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def payment_params
|
|
72
|
+
params.require(:credit_card).permit(:first_name, :last_name, :number, :cvv,
|
|
73
|
+
:expiration_month, :expiration_year)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def check_step(current_step)
|
|
77
|
+
@steps.index(current_step) <= @current_order.step if @current_order.step
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def update_step(current_step)
|
|
81
|
+
if @current_order.step == @steps.index(current_step)
|
|
82
|
+
@current_order.update(step: @current_order.step + 1)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def check_complete
|
|
87
|
+
if @steps
|
|
88
|
+
redirect_to :back unless @current_order.step == @steps.count
|
|
89
|
+
else
|
|
90
|
+
redirect_to :back unless @current_order.address
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def redirect_by_step(current_step)
|
|
95
|
+
return redirect_to '/shopping_cart/checkout/complete' if !@steps || current_step == @steps.last
|
|
96
|
+
redirect_to "/shopping_cart/checkout/#{@steps[@steps.index(current_step) + 1]}"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require_dependency "shopping_cart/application_controller"
|
|
2
|
+
|
|
3
|
+
module ShoppingCart
|
|
4
|
+
class OrderItemsController < ApplicationController
|
|
5
|
+
load_and_authorize_resource only: [:destroy, :update]
|
|
6
|
+
|
|
7
|
+
before_action :get_order
|
|
8
|
+
before_action :check_order, except: [:index, :create]
|
|
9
|
+
|
|
10
|
+
def create
|
|
11
|
+
model = params['model'].constantize
|
|
12
|
+
product = model.find(params['id'])
|
|
13
|
+
return unless product.respond_to?(:price) && OrderItem.new(quantity: params[:quantity]).valid?
|
|
14
|
+
@current_order ||= Order.create(user: @user)
|
|
15
|
+
order_item = @current_order.order_items.find_by(productable: product)
|
|
16
|
+
if order_item
|
|
17
|
+
order_item.update(quantity: params[:quantity])
|
|
18
|
+
else
|
|
19
|
+
@current_order.order_items.create(productable: product,
|
|
20
|
+
price: product.price, quantity: params[:quantity])
|
|
21
|
+
end
|
|
22
|
+
flash[:notice] = t('shopping_cart.item_added')
|
|
23
|
+
redirect_to root_path
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def update
|
|
27
|
+
flash[:notice] = t('shopping_cart.cart_updated') if @order_item.update(quantity: params[:quantity])
|
|
28
|
+
redirect_to root_path
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def destroy
|
|
32
|
+
@order_item.destroy
|
|
33
|
+
redirect_to root_path
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def destroy_items
|
|
37
|
+
@current_order.order_items.destroy_all
|
|
38
|
+
redirect_to root_path
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def discount
|
|
42
|
+
discount = Discount.find_by_code(params[:code])
|
|
43
|
+
if discount && discount.order_id.nil?
|
|
44
|
+
discount.update(order: @current_order)
|
|
45
|
+
flash[:notice] = t('shopping_cart.valid_coupon', amount: discount.amount)
|
|
46
|
+
end
|
|
47
|
+
redirect_to root_path
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
end
|