cartify 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +69 -0
  4. data/Rakefile +32 -0
  5. data/app/assets/config/cartify_manifest.js +2 -0
  6. data/app/assets/javascripts/cartify/application.js +14 -0
  7. data/app/assets/javascripts/cartify/checkout.js +11 -0
  8. data/app/assets/javascripts/cartify/masks.js +10 -0
  9. data/app/assets/javascripts/cartify/orders.js +7 -0
  10. data/app/assets/stylesheets/cartify/application.css +15 -0
  11. data/app/concerns/cartify/authenticatable.rb +17 -0
  12. data/app/concerns/cartify/current_session.rb +52 -0
  13. data/app/concerns/cartify/showable.rb +41 -0
  14. data/app/concerns/cartify/updatable.rb +39 -0
  15. data/app/controllers/cartify/addresses_controller.rb +18 -0
  16. data/app/controllers/cartify/application_controller.rb +6 -0
  17. data/app/controllers/cartify/carts_controller.rb +20 -0
  18. data/app/controllers/cartify/checkout_controller.rb +29 -0
  19. data/app/controllers/cartify/order_items_controller.rb +35 -0
  20. data/app/controllers/cartify/orders_controller.rb +17 -0
  21. data/app/decorator/cartify/order_decorator.rb +39 -0
  22. data/app/forms/cartify/addresses_form.rb +71 -0
  23. data/app/helpers/cartify/application_helper.rb +18 -0
  24. data/app/helpers/cartify/carts_helper.rb +10 -0
  25. data/app/helpers/cartify/checkout_helper.rb +22 -0
  26. data/app/helpers/cartify/order_helper.rb +12 -0
  27. data/app/models/cartify/address.rb +23 -0
  28. data/app/models/cartify/application_record.rb +5 -0
  29. data/app/models/cartify/billing.rb +4 -0
  30. data/app/models/cartify/coupon.rb +10 -0
  31. data/app/models/cartify/credit_card.rb +25 -0
  32. data/app/models/cartify/delivery.rb +8 -0
  33. data/app/models/cartify/order.rb +64 -0
  34. data/app/models/cartify/order_item.rb +39 -0
  35. data/app/models/cartify/order_status.rb +18 -0
  36. data/app/models/cartify/shipping.rb +4 -0
  37. data/app/queries/cartify/orders_query.rb +17 -0
  38. data/app/views/cartify/addresses/index.haml +9 -0
  39. data/app/views/cartify/carts/_order_items.haml +17 -0
  40. data/app/views/cartify/carts/_order_summary.haml +15 -0
  41. data/app/views/cartify/carts/_quantity.haml +7 -0
  42. data/app/views/cartify/carts/_xs_order_items.haml +24 -0
  43. data/app/views/cartify/carts/show.html.haml +22 -0
  44. data/app/views/cartify/checkout/_order_summary.haml +4 -0
  45. data/app/views/cartify/checkout/_progress.haml +21 -0
  46. data/app/views/cartify/checkout/addresses.haml +35 -0
  47. data/app/views/cartify/checkout/complete.haml +19 -0
  48. data/app/views/cartify/checkout/confirm.haml +27 -0
  49. data/app/views/cartify/checkout/delivery.haml +11 -0
  50. data/app/views/cartify/checkout/login.haml +39 -0
  51. data/app/views/cartify/checkout/partials/_addresses_short.haml +12 -0
  52. data/app/views/cartify/checkout/partials/_delivery-lg.haml +22 -0
  53. data/app/views/cartify/checkout/partials/_delivery-xs.haml +22 -0
  54. data/app/views/cartify/checkout/partials/_list_of_items.haml +54 -0
  55. data/app/views/cartify/checkout/payment.haml +27 -0
  56. data/app/views/cartify/order_items/create.js.erb +5 -0
  57. data/app/views/cartify/order_items/destroy.js.erb +3 -0
  58. data/app/views/cartify/order_items/update.js.erb +3 -0
  59. data/app/views/cartify/orders/_filters.haml +16 -0
  60. data/app/views/cartify/orders/_orders-lg.haml +10 -0
  61. data/app/views/cartify/orders/_orders-xs.haml +23 -0
  62. data/app/views/cartify/orders/index.haml +21 -0
  63. data/app/views/cartify/orders/show.haml +28 -0
  64. data/app/views/cartify/shared/_address.haml +28 -0
  65. data/app/views/cartify/shared/_addresses_form.haml +31 -0
  66. data/app/views/cartify/shared/_checkout_summary_numbers.haml +15 -0
  67. data/app/views/cartify/shared/_order_summary_numbers.haml +15 -0
  68. data/config/locales/en.yml +135 -0
  69. data/config/routes.rb +9 -0
  70. data/db/migrate/20171005130857_create_cartify_coupons.rb +8 -0
  71. data/db/migrate/20171005131198_create_cartify_credit_cards.rb +12 -0
  72. data/db/migrate/20171005131199_create_cartify_deliveries.rb +9 -0
  73. data/db/migrate/20171005131200_create_cartify_order_statuses.rb +7 -0
  74. data/db/migrate/20171005131201_create_cartify_orders.rb +19 -0
  75. data/db/migrate/20171005131202_create_cartify_order_items.rb +14 -0
  76. data/db/migrate/20171006133037_create_cartify_addresses.rb +17 -0
  77. data/lib/cartify.rb +19 -0
  78. data/lib/cartify/engine.rb +30 -0
  79. data/lib/cartify/version.rb +5 -0
  80. data/lib/generators/initializer/USAGE +49 -0
  81. data/lib/generators/initializer/initializer_generator.rb +17 -0
  82. data/lib/generators/initializer/templates/initializer.rb +10 -0
  83. data/lib/tasks/cartify_tasks.rake +4 -0
  84. metadata +478 -0
@@ -0,0 +1,23 @@
1
+ - @orders.each do |order|
2
+ .general-cart-item.divider-lg-bottom.pt-0
3
+ %table.table
4
+ %tr
5
+ %td.col-half
6
+ %span.in-grey-600= t('order.number')
7
+ %td.col-half
8
+ %span.general-order-number= order.number
9
+ %tr
10
+ %td
11
+ %span.in-grey-600= t('order.complited_at')
12
+ %td
13
+ %span.in-grey-900.font-16.fw-300= order.complited_at
14
+ %tr
15
+ %td
16
+ %span.in-grey-600= t('order.statuss')
17
+ %td
18
+ %span.font-16.in-grey-900.fw-300= order.status
19
+ %tr
20
+ %td
21
+ %span.in-grey-600= t('order.total')
22
+ %td
23
+ %strong.font-16= number_to_currency order.total
@@ -0,0 +1,21 @@
1
+ %main.container.general-main-wrap
2
+ %h1.mt-0.mb-25= t('orders.my_orders')
3
+ %p.lead.small.mb-10.visible-xs= t('page.book.index.sort_by')
4
+ .dropdowns.dropdown.general-order-dropdown
5
+ = render 'cartify/orders/filters'
6
+ .visible-xs
7
+ = render 'cartify/orders/orders-xs'
8
+ .hidden-xs.mb-res-50
9
+ %table.table.table-hover
10
+ %thead
11
+ %tr
12
+ %th
13
+ %span.in-grey-600= t('order.number')
14
+ %th
15
+ %span.in-grey-600= t('order.complited_at')
16
+ %th
17
+ %span.in-grey-600= t('order.statuss')
18
+ %th
19
+ %span.in-grey-600= t('order.total')
20
+ %tbody
21
+ = render 'cartify/orders/orders-lg'
@@ -0,0 +1,28 @@
1
+ %main.container.general-main-wrap
2
+ =link_to orders_path, class: 'general-back-link' do
3
+ %i.fa.fa-long-arrow-left.mr-15
4
+ = t('button.back_to_orders')
5
+ .row.mb-20
6
+ .col-sm-3
7
+ %h3.general-subtitle= t('settings.shipping')
8
+ = render 'cartify/checkout/partials/addresses_short', address: @order.shipping, step: :complete
9
+ .col-sm-3
10
+ %h3.general-subtitle= t('settings.billing')
11
+ = render 'cartify/checkout/partials/addresses_short', address: @order.billing, step: :complete
12
+ .col-sm-3
13
+ %h3.general-subtitle= t('confirm.shipments')
14
+ %p.general-address
15
+ = @order.delivery.name
16
+ %br
17
+ = @order.delivery.duration
18
+ .col-sm-3
19
+ %h3.general-subtitle= t('confirm.payment_info')
20
+ %p.general-address
21
+ = @order.secret_card_number
22
+ %br
23
+ = @order.credit_card.mm_yy
24
+ = render 'cartify/checkout/partials/list_of_items', current_order: @order
25
+ %div.text-center.general-text-right
26
+ %p.in-gold-500.font-18= t('cart.summary')
27
+ %table.general-summary-table.general-summary-table-right.general-text-right
28
+ = render 'cartify/shared/checkout_summary_numbers', current_order: @order, step: :comfirm
@@ -0,0 +1,28 @@
1
+ .form-group{ class: "#{errors.messages[:first_name].empty? ? '' : 'has-error'}" }
2
+ = ff.label :first_name, t('settings.first_name'), class: 'control-label input-label'
3
+ = ff.text_field :first_name, class: 'form-control', autocomplete: 'off', placeholder: t('settings.first_name')
4
+ %span.help-block= errors.messages[:first_name].to_sentence
5
+ .form-group{ class: "#{errors.messages[:last_name].empty? ? '' : 'has-error'}" }
6
+ = ff.label :last_name, class: 'control-label input-label'
7
+ = ff.text_field :last_name, class: 'form-control', autocomplete: 'off', placeholder: t('settings.last_name')
8
+ %span.help-block= errors.messages[:last_name].to_sentence
9
+ .form-group{ class: "#{errors.messages[:address].empty? ? '' : 'has-error'}" }
10
+ = ff.label :address, t('settings.address'), class: 'control-label input-label'
11
+ = ff.text_field :address, class: 'form-control', autocomplete: 'off', placeholder: t('settings.address')
12
+ %span.help-block= errors.messages[:address].to_sentence
13
+ .form-group{ class: "#{errors.messages[:city].empty? ? '' : 'has-error'}" }
14
+ = ff.label :city, t('settings.city'), class: 'control-label input-label'
15
+ = ff.text_field :city, class: 'form-control', autocomplete: 'off', placeholder: t('settings.city')
16
+ %span.help-block= errors.messages[:city].to_sentence
17
+ .form-group{ class: "#{errors.messages[:zip].empty? ? '' : 'has-error'}" }
18
+ = ff.label :zip, t('settings.zip'), class: 'control-label input-label'
19
+ = ff.text_field :zip, class: 'form-control', autocomplete: 'off', placeholder: t('settings.zip')
20
+ %span.help-block= errors.messages[:zip].to_sentence
21
+ .form-group{ class: "#{errors.messages[:country].empty? ? '' : 'has-error'}" }
22
+ = ff.label :country, t('settings.country'), class: 'control-label input-label'
23
+ = ff.country_select(:country, { priority_countries: ['UA'], placeholder: t('settings.country_select') }, { class: 'form-control', id: 'country' } )
24
+ %span.help-block= errors.messages[:country].to_sentence
25
+ .form-group.mb-55{ class: "#{errors.messages[:phone].empty? ? '' : 'has-error'}" }
26
+ = ff.label :phone, 'Phone', class: 'control-label input-label'
27
+ = ff.text_field :phone, class: 'form-control', autocomplete: 'off', placeholder: 'Example +355 66 123 4567'
28
+ %span.help-block= errors.messages[:phone].to_sentence
@@ -0,0 +1,31 @@
1
+ #addres.general-main-wrap.tab-pane.fade.in.active{role: "tabpanel"}
2
+ = form_for @addresses, url: settings_addresses_path do |f|
3
+ .hidden-xs.hidden-sm
4
+ .row
5
+ .col-md-5
6
+ %h3.general-subtitle.mt-0= t('settings.billing')
7
+ .col-md-5.col-md-offset-1
8
+ %h3.general-subtitle.mt-0= t('settings.shipping')
9
+ .row
10
+ .col-md-5.mb-40
11
+ .visible-xs.visible-sm
12
+ %h3.general-subtitle.mt-0= t('settings.billing')
13
+ = f.fields_for @addresses.billing do |ff|
14
+ - bill_errors = @addresses.billing.errors
15
+ = render 'cartify/shared/address', ff: ff, errors: bill_errors
16
+ = f.hidden_field "[billing][user_id]", value: cartify_current_user.id
17
+ .col-md-5.col-md-offset-1.mb-25
18
+ .visible-xs.visible-sm
19
+ %h3.general-subtitle.mt-0= t('settings.shipping')
20
+ = f.fields_for @addresses.shipping do |ff|
21
+ - ship_errors = @addresses.shipping.errors
22
+ = render 'cartify/shared/address', ff: ff, errors: ship_errors
23
+ = f.hidden_field "[shipping][user_id]", value: cartify_current_user.id
24
+ .form-group.checkbox
25
+ %label.checkbox-label
26
+ = f.check_box :use_billing, class: 'checkbox-input', id: 'use_billing', hidden: true
27
+ %span.checkbox-icon
28
+ %i.fa.fa-check
29
+ %span.checkbox-text= t('settings.use_billing')
30
+ .ral-text-align.mb-60
31
+ = f.submit t('settings.save'), class: 'button btn btn-default center-block mb-20'
@@ -0,0 +1,15 @@
1
+ %tr
2
+ %td
3
+ %p.font-16= t('confirm.item_total')+':'
4
+ %td
5
+ %p.font-16= number_to_currency current_order.subtotal_item_total
6
+ %tr
7
+ %td
8
+ %p.font-16= t('confirm.shipping')+':' unless step == :address
9
+ %td
10
+ %p.font-16= number_to_currency current_order.shipping_price unless step == :address
11
+ %tr
12
+ %td
13
+ %strong.font-16= t('cart.total')+':'
14
+ %td
15
+ %strong.font-16= number_to_currency current_order.total || '--'
@@ -0,0 +1,15 @@
1
+ %tr
2
+ %td
3
+ %p.font-16= t('cart.subtotal')+':'
4
+ %td
5
+ %p.font-16= number_to_currency current_order.subtotal
6
+ %tr
7
+ %td
8
+ %p.font-16= t('cart.coupon')+':'
9
+ %td
10
+ %p.font-16= number_to_currency current_order.discount
11
+ %tr
12
+ %td
13
+ %strong.font-16= t('cart.total')+':'
14
+ %td
15
+ %strong.font-16= number_to_currency current_order.total || '--'
@@ -0,0 +1,135 @@
1
+ en:
2
+ button:
3
+ orders: 'Orders'
4
+ settings: 'Settings'
5
+ back_to_store: 'Back to Store'
6
+ back_to_orders: 'Back to Orders'
7
+ change: 'Change'
8
+ by_now: 'Buy Now'
9
+ back_to_results: 'Back to results'
10
+ add_to_cart: 'Add to Cart'
11
+ all: 'All'
12
+ get_started: 'Get Started'
13
+ post: 'Post'
14
+
15
+
16
+ number:
17
+ currency:
18
+ format:
19
+ unit: '€'
20
+ delimiter: ','
21
+ separator: '.'
22
+ precision: 2
23
+ format: '%u%n'
24
+
25
+ cart:
26
+ cart: 'Cart'
27
+ product: 'Product'
28
+ price: 'Price'
29
+ quantity: 'Quantity'
30
+ subtotal: 'SubTotal'
31
+ checkout: 'Checkout'
32
+ coupon: 'Enter Your Coupon Code'
33
+ apply_coupon: 'Apply Coupon'
34
+ update: 'Update cart'
35
+ summary: 'Order Summary'
36
+ coupon: 'Coupon'
37
+ total: 'Order Total'
38
+
39
+ settings:
40
+ settings: 'Settings'
41
+ address: 'Address'
42
+ privacy: 'Privacy'
43
+ billing: 'Billing Address'
44
+ shipping: 'Shipping Address'
45
+ use_billing: 'Use Billing Adress'
46
+ save: 'Save'
47
+ e-mail: 'Email'
48
+ enter_e-mail: 'Enter Email'
49
+ enter_your_e-mail: 'Enter your email'
50
+ password: 'Password'
51
+ old_password: 'Old Password'
52
+ new_password: 'New Password'
53
+ confirm_password: 'Confirm Password'
54
+ remove_account: 'Remove Account'
55
+ please_remove_account: 'Please Remove My Account'
56
+ im_agree_lost_all_data: 'I understand that all data will be lost'
57
+ first_name: 'First Name'
58
+ last_name: 'Last Name'
59
+ city: 'City'
60
+ zip: 'Zip'
61
+ country: 'Country'
62
+ country_select: 'Select a country'
63
+ user:
64
+ seccess_updating_email: 'E-mail was successfully updated.'
65
+ updated: 'Data was successfully updated'
66
+ order:
67
+ my_orders: 'My Orders'
68
+ number: 'Number'
69
+ complited_at: 'Completed at'
70
+ statuss: 'Status'
71
+ total: 'Total'
72
+ status:
73
+ in_progress: 'In Progress'
74
+ in_queue: 'Waiting for processing'
75
+ in_delivery: 'In delivery'
76
+ delivered: 'Delivered'
77
+ canceled: 'Cenceled'
78
+ all: 'All Orders'
79
+
80
+ delivery:
81
+ delivery_method: 'Shipping Method'
82
+ method: 'Method'
83
+ days: 'Days'
84
+ price: 'Price'
85
+ pickup: 'Please, pick up Delivery type!'
86
+
87
+ credit_card:
88
+ tooltip: '3-digit security code, usually found on the back of your card. American Express cards has 4-digit code, located on the front'
89
+ confirm:
90
+ shipments: 'Shipments'
91
+ payment_info: 'Payment Information'
92
+ edit: 'edit'
93
+ delivery: 'Delivery'
94
+ paymant: 'Payment'
95
+ confirm: 'Confirm'
96
+ complete: 'Complete'
97
+ shipping: 'Shipping'
98
+ item_total: 'Item Total'
99
+ complete:
100
+ order: 'Order'
101
+ thanks: 'Thank You for your Order!'
102
+ has_been_sent_to: 'An order confirmation has been sent to '
103
+ book: 'Book'
104
+ date:
105
+ month_names: [nil, 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
106
+
107
+ flash:
108
+ coupon_applied: 'Coupon was successfully applied'
109
+ fake_coupon: 'Unavailable coupon, please try another'
110
+ notice:
111
+ wrong_password: 'Please enter correct password'
112
+
113
+ checkout:
114
+ save_and_continue: 'Save and Continue'
115
+ place_order: 'Place Order'
116
+ cart_number: 'Card Number'
117
+ credit_card: 'Credit Card'
118
+ name_on_card: 'Name on Card'
119
+ mm_yy: 'MM / YY'
120
+ cvv: 'CVV'
121
+ returning_customer: 'Returning Customer'
122
+ or: 'or'
123
+ login_with_password: 'Log in with password'
124
+ enter_email: 'Enter Email'
125
+ new_customer: 'New Customer'
126
+ quick_register: 'Quick Register'
127
+ quick_description: "You'll be able to create password later"
128
+ continue: 'Continue to Checkout'
129
+ add_to_cart: 'Add to cart'
130
+ cart: 'Cart'
131
+
132
+ validation:
133
+ cart_number: 'Please enter a valid credit card number'
134
+ cart_name: 'Allow only letters'
135
+ mm_yy: 'Mons can be from 01 to 12'
@@ -0,0 +1,9 @@
1
+ Cartify::Engine.routes.draw do
2
+ resource :cart, only: %i[show update]
3
+ resources :order_items, only: %i[create update destroy]
4
+ resources :orders, only: %i[index show]
5
+ resources :checkout
6
+
7
+ match 'settings/addresses', to: 'addresses#index', via: 'get'
8
+ match 'settings/addresses', to: 'addresses#create', via: 'post'
9
+ end
@@ -0,0 +1,8 @@
1
+ class CreateCartifyCoupons < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :cartify_coupons do |t|
4
+ t.string :name
5
+ t.decimal :value, precision: 8, scale: 2
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ class CreateCartifyCreditCards < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :cartify_credit_cards do |t|
4
+ t.string :number
5
+ t.string :name
6
+ t.string :mm_yy
7
+ t.string :cvv
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ class CreateCartifyDeliveries < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :cartify_deliveries do |t|
4
+ t.string :name
5
+ t.string :duration
6
+ t.decimal :price, precision: 8, scale: 2
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ class CreateCartifyOrderStatuses < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :cartify_order_statuses do |t|
4
+ t.string :name
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ class CreateCartifyOrders < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :cartify_orders do |t|
4
+ t.decimal :subtotal, precision: 8, scale: 2
5
+ t.decimal :total, precision: 8, scale: 2
6
+ t.integer :user_id, foreign_key: true
7
+ t.references :order_status,
8
+ foreign_key: { to_table: :cartify_order_statuses }
9
+ t.references :coupon,
10
+ foreign_key: { to_table: :cartify_coupons }
11
+ t.references :delivery,
12
+ foreign_key: { to_table: :cartify_deliveries }
13
+ t.references :credit_card,
14
+ foreign_key: { to_table: :cartify_credit_cards }
15
+
16
+ t.timestamps
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ class CreateCartifyOrderItems < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :cartify_order_items do |t|
4
+ t.integer :quantity
5
+ t.decimal :unit_price, precision: 8, scale: 2
6
+ t.decimal :total_price, precision: 8, scale: 2
7
+ t.integer :product_id, foreign_key: true
8
+ t.references :order,
9
+ foreign_key: { to_table: :cartify_orders }
10
+
11
+ t.timestamps
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ class CreateCartifyAddresses < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :cartify_addresses do |t|
4
+ t.string :type
5
+ t.string :first_name
6
+ t.string :last_name
7
+ t.string :address
8
+ t.string :city
9
+ t.integer :zip
10
+ t.string :country
11
+ t.string :phone
12
+ t.integer :user_id, foreign_key: true
13
+ t.references :order,
14
+ foreign_key: { to_table: :cartify_orders }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ require 'cartify/engine'
2
+
3
+ module Cartify
4
+ mattr_accessor :product_class
5
+ mattr_accessor :user_class
6
+ mattr_accessor :empty_cart_path
7
+
8
+ def self.product_class
9
+ @@product_class.constantize
10
+ end
11
+
12
+ def self.user_class
13
+ @@user_class.constantize
14
+ end
15
+
16
+ def self.table_name_prefix
17
+ 'cartify_'
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ module Cartify
2
+ class Engine < ::Rails::Engine
3
+ require 'jquery-rails'
4
+ require 'country_select'
5
+ require 'jquery_mask_rails'
6
+ require 'wicked'
7
+ require 'virtus'
8
+ require 'draper'
9
+ require 'devise'
10
+ require 'haml-rails'
11
+ require 'pry-byebug'
12
+
13
+ isolate_namespace Cartify
14
+
15
+ config.generators do |g|
16
+ g.test_framework :rspec
17
+ g.fixture_replacement :factory_girl, dir: 'spec/factories'
18
+ g.stylesheets false
19
+ g.javascripts false
20
+ g.template_engine :haml
21
+ end
22
+
23
+ initializer :inject_helpers do
24
+ ActiveSupport.on_load :action_controller do
25
+ ::ApplicationController.send(:helper, Cartify::Engine.helpers)
26
+ ::ApplicationController.send(:include, Cartify::CurrentSession)
27
+ end
28
+ end
29
+ end
30
+ end