shopping_cart 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +100 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/config/shopping_cart_manifest.js +2 -0
  6. data/app/assets/images/shopping_cart/back.png +0 -0
  7. data/app/assets/images/shopping_cart/delete.png +0 -0
  8. data/app/assets/images/shopping_cart/empty_cart.png +0 -0
  9. data/app/assets/images/shopping_cart/refresh.png +0 -0
  10. data/app/assets/javascripts/shopping_cart/application.js +4 -0
  11. data/app/assets/javascripts/shopping_cart/order_items.js.coffee +4 -0
  12. data/app/assets/stylesheets/shopping_cart/application.scss +3 -0
  13. data/app/assets/stylesheets/shopping_cart/order_items.scss +7 -0
  14. data/app/controllers/shopping_cart/application_controller.rb +23 -0
  15. data/app/controllers/shopping_cart/checkout_controller.rb +99 -0
  16. data/app/controllers/shopping_cart/order_items_controller.rb +51 -0
  17. data/app/helpers/shopping_cart/application_helper.rb +4 -0
  18. data/app/helpers/shopping_cart/order_items_helper.rb +7 -0
  19. data/app/helpers/shopping_cart/orders_helper.rb +7 -0
  20. data/app/jobs/shopping_cart/application_job.rb +4 -0
  21. data/app/mailers/shopping_cart/application_mailer.rb +6 -0
  22. data/app/models/ability.rb +8 -0
  23. data/app/models/shopping_cart/address.rb +7 -0
  24. data/app/models/shopping_cart/application_record.rb +5 -0
  25. data/app/models/shopping_cart/credit_card.rb +9 -0
  26. data/app/models/shopping_cart/discount.rb +5 -0
  27. data/app/models/shopping_cart/order.rb +47 -0
  28. data/app/models/shopping_cart/order_item.rb +9 -0
  29. data/app/models/shopping_cart/shipping.rb +4 -0
  30. data/app/views/layouts/shopping_cart/application.html.haml +12 -0
  31. data/app/views/layouts/shopping_cart/checkout.html.haml +30 -0
  32. data/app/views/shopping_cart/checkout/complete.html.haml +54 -0
  33. data/app/views/shopping_cart/checkout/index.html.haml +23 -0
  34. data/app/views/shopping_cart/checkout/payment.html.haml +26 -0
  35. data/app/views/shopping_cart/checkout/shipping.html.haml +6 -0
  36. data/app/views/shopping_cart/order_items/index.html.haml +68 -0
  37. data/config/locales/shopping_cart.en.yml +35 -0
  38. data/config/routes.rb +16 -0
  39. data/db/migrate/20160810062129_create_shopping_cart_order_items.rb +11 -0
  40. data/db/migrate/20160810063613_create_shopping_cart_orders.rb +14 -0
  41. data/db/migrate/20160811075244_create_shopping_cart_discounts.rb +10 -0
  42. data/db/migrate/20160815065526_create_shopping_cart_addresses.rb +13 -0
  43. data/db/migrate/20160816093049_create_shopping_cart_shippings.rb +8 -0
  44. data/db/migrate/20160817082741_create_shopping_cart_credit_cards.rb +13 -0
  45. data/lib/generators/shopping_cart/config_generator.rb +24 -0
  46. data/lib/generators/shopping_cart/controller_generator.rb +47 -0
  47. data/lib/generators/shopping_cart/translate_generator.rb +43 -0
  48. data/lib/generators/shopping_cart/views_generator.rb +17 -0
  49. data/lib/shopping_cart.rb +7 -0
  50. data/lib/shopping_cart/engine.rb +15 -0
  51. data/lib/shopping_cart/version.rb +3 -0
  52. data/lib/tasks/shopping_cart_tasks.rake +4 -0
  53. 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
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts/shopping_cart .js
2
+ //= link_directory ../stylesheets/shopping_cart .css
@@ -0,0 +1,4 @@
1
+ //= require jquery
2
+ //= require jquery_ujs
3
+ //= require bootstrap-sprockets
4
+ //= require_tree .
@@ -0,0 +1,4 @@
1
+ $(document).on 'turbolinks:load, ready', ->
2
+ $('#coupon').on 'click', ->
3
+ event.preventDefault()
4
+ $('#add_couponModal').modal({backdrop: 'static'})
@@ -0,0 +1,3 @@
1
+ @import "bootstrap-sprockets";
2
+ @import "bootstrap";
3
+ @import "order_items";
@@ -0,0 +1,7 @@
1
+ .alert {
2
+ color: red;
3
+ }
4
+
5
+ .notice {
6
+ color: green;
7
+ }
@@ -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
@@ -0,0 +1,4 @@
1
+ module ShoppingCart
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,7 @@
1
+ module ShoppingCart
2
+ module OrderItemsHelper
3
+ def total_for_item(item)
4
+ item.price * item.quantity
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module ShoppingCart
2
+ module OrdersHelper
3
+ def calculate_discount(order)
4
+ order.discount_amount ? order.subtotal_price * (order.discount_amount.to_f / 100) : 0
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ module ShoppingCart
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module ShoppingCart
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ class Ability
2
+ include CanCan::Ability
3
+
4
+ def initialize(user)
5
+ alias_action :update, :destroy, to: :modify
6
+ can :modify, ShoppingCart::OrderItem, order_id: ShoppingCart::Order.current_order(user).id
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module ShoppingCart
2
+ class Address < ApplicationRecord
3
+ belongs_to :order
4
+
5
+ validates :first_name, :last_name, :phone, presence: true
6
+ end
7
+ end