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.
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