shopping_cart 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|