comable-core 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +97 -0
  4. data/app/assets/javascripts/comable/application.js +13 -0
  5. data/app/assets/stylesheets/comable/application.css +13 -0
  6. data/app/controllers/concerns/comable/permitted_attributes.rb +15 -0
  7. data/app/helpers/comable/application_helper.rb +84 -0
  8. data/app/helpers/comable/products_helper.rb +82 -0
  9. data/app/mailers/comable/order_mailer.rb +18 -0
  10. data/app/models/comable/ability.rb +18 -0
  11. data/app/models/comable/address.rb +39 -0
  12. data/app/models/comable/category.rb +72 -0
  13. data/app/models/comable/image.rb +15 -0
  14. data/app/models/comable/order.rb +121 -0
  15. data/app/models/comable/order/associations.rb +22 -0
  16. data/app/models/comable/order/callbacks.rb +43 -0
  17. data/app/models/comable/order/morrisable.rb +20 -0
  18. data/app/models/comable/order/scopes.rb +17 -0
  19. data/app/models/comable/order/validations.rb +39 -0
  20. data/app/models/comable/order_item.rb +98 -0
  21. data/app/models/comable/order_item/csvable.rb +32 -0
  22. data/app/models/comable/page.rb +30 -0
  23. data/app/models/comable/payment.rb +87 -0
  24. data/app/models/comable/payment_method.rb +27 -0
  25. data/app/models/comable/product.rb +55 -0
  26. data/app/models/comable/product/csvable.rb +20 -0
  27. data/app/models/comable/shipment.rb +85 -0
  28. data/app/models/comable/shipment_method.rb +9 -0
  29. data/app/models/comable/stock.rb +71 -0
  30. data/app/models/comable/stock/csvable.rb +26 -0
  31. data/app/models/comable/store.rb +30 -0
  32. data/app/models/comable/theme.rb +25 -0
  33. data/app/models/comable/tracker.rb +17 -0
  34. data/app/models/comable/user.rb +130 -0
  35. data/app/models/concerns/comable/cart_owner.rb +94 -0
  36. data/app/models/concerns/comable/checkout.rb +85 -0
  37. data/app/models/concerns/comable/importable.rb +67 -0
  38. data/app/models/concerns/comable/liquidable.rb +12 -0
  39. data/app/models/concerns/comable/product/search.rb +41 -0
  40. data/app/models/concerns/comable/ransackable.rb +38 -0
  41. data/app/models/concerns/comable/role_owner.rb +15 -0
  42. data/app/models/concerns/comable/sku_choice.rb +19 -0
  43. data/app/models/concerns/comable/sku_item.rb +17 -0
  44. data/app/uploaders/image_uploader.rb +7 -0
  45. data/app/views/comable/order_mailer/complete.text.erb +42 -0
  46. data/config/initializers/comma.rb +8 -0
  47. data/config/locales/en.yml +424 -0
  48. data/config/locales/ja.yml +425 -0
  49. data/db/migrate/20131214194807_create_comable_products.rb +15 -0
  50. data/db/migrate/20140120032559_create_comable_users.rb +46 -0
  51. data/db/migrate/20140502060116_create_comable_stocks.rb +14 -0
  52. data/db/migrate/20140723175431_create_comable_orders.rb +20 -0
  53. data/db/migrate/20140723175810_create_comable_order_items.rb +19 -0
  54. data/db/migrate/20140817194104_create_comable_payment_methods.rb +13 -0
  55. data/db/migrate/20140921191416_create_comable_shipment_methods.rb +11 -0
  56. data/db/migrate/20140926063541_create_comable_stores.rb +11 -0
  57. data/db/migrate/20141024025526_create_comable_addresses.rb +17 -0
  58. data/db/migrate/20150111031228_create_comable_categories.rb +10 -0
  59. data/db/migrate/20150111031229_create_comable_products_categories.rb +8 -0
  60. data/db/migrate/20150112173706_create_comable_images.rb +9 -0
  61. data/db/migrate/20150423095210_create_comable_shipments.rb +13 -0
  62. data/db/migrate/20150511171940_create_comable_payments.rb +12 -0
  63. data/db/migrate/20150513185230_create_comable_trackers.rb +12 -0
  64. data/db/migrate/20150519080729_create_comable_pages.rb +17 -0
  65. data/db/migrate/20150612143226_create_comable_themes.rb +15 -0
  66. data/db/migrate/20150612143445_add_theme_id_to_comable_stores.rb +7 -0
  67. data/db/seeds.rb +5 -0
  68. data/db/seeds/comable/users.rb +51 -0
  69. data/lib/comable/core.rb +48 -0
  70. data/lib/comable/core/configuration.rb +22 -0
  71. data/lib/comable/core/engine.rb +50 -0
  72. data/lib/comable/deprecator.rb +26 -0
  73. data/lib/comable/payment_provider.rb +14 -0
  74. data/lib/comable/payment_provider/base.rb +42 -0
  75. data/lib/comable/payment_provider/general.rb +15 -0
  76. data/lib/comable/state_machine_patch.rb +32 -0
  77. data/lib/comma_extractor_extentions.rb +31 -0
  78. data/lib/generators/comable/install/install_generator.rb +133 -0
  79. data/lib/generators/comable/install/templates/config/initializers/comable.rb +31 -0
  80. data/lib/tasks/comable_tasks.rake +4 -0
  81. metadata +346 -0
@@ -0,0 +1,17 @@
1
+ module Comable
2
+ class Tracker < ActiveRecord::Base
3
+ extend Enumerize
4
+
5
+ validates :name, presence: true, length: { maximum: 255 }
6
+ validates :tracker_id, length: { maximum: 255 }
7
+ validates :code, presence: true
8
+ validates :place, presence: true, length: { maximum: 255 }
9
+
10
+ scope :activated, -> { where(activated_flag: true) }
11
+
12
+ enumerize :place, in: %i(
13
+ everywhere
14
+ checkout
15
+ ), scope: true
16
+ end
17
+ end
@@ -0,0 +1,130 @@
1
+ module Comable
2
+ class User < ActiveRecord::Base
3
+ include Comable::CartOwner
4
+ include Comable::RoleOwner
5
+ include Comable::Ransackable
6
+ include Comable::Liquidable
7
+
8
+ has_many :orders, class_name: Comable::Order.name
9
+ has_many :addresses, class_name: Comable::Address.name, dependent: :destroy
10
+ belongs_to :bill_address, class_name: Comable::Address.name, dependent: :destroy
11
+ belongs_to :ship_address, class_name: Comable::Address.name, dependent: :destroy
12
+
13
+ accepts_nested_attributes_for :addresses
14
+ accepts_nested_attributes_for :bill_address
15
+ accepts_nested_attributes_for :ship_address
16
+
17
+ scope :this_month, -> { where(created_at: Time.now.beginning_of_month..Time.now.end_of_month) }
18
+ scope :this_week, -> { where(created_at: Time.now.beginning_of_week..Time.now.end_of_week) }
19
+ scope :last_week, -> { where(created_at: 1.week.ago.beginning_of_week..1.week.ago.end_of_week) }
20
+
21
+ validates :email, presence: true, length: { maximum: 255 }
22
+
23
+ devise(*Comable::Config.devise_strategies[:user])
24
+
25
+ ransack_options ransackable_attributes: { except: [:encrypted_password, :reset_password_token, :reset_password_sent_at, :remember_created_at, :bill_address_id, :ship_address_id] }
26
+
27
+ liquid_methods :email, :bill_full_name, :ship_full_name, :cart
28
+
29
+ delegate :full_name, to: :bill_address, allow_nil: true, prefix: :bill
30
+ delegate :full_name, to: :ship_address, allow_nil: true, prefix: :ship
31
+
32
+ def with_cookies(cookies)
33
+ @cookies = cookies
34
+ self
35
+ end
36
+
37
+ # Add conditions for the orders association.
38
+ # Override method of the orders association to support Rails 3.x.
39
+ def orders
40
+ super.complete.order('completed_at DESC, id DESC')
41
+ end
42
+
43
+ def other_addresses
44
+ addresses - [bill_address] - [ship_address]
45
+ end
46
+
47
+ def update_bill_address_by(bill_address)
48
+ update_attributes(bill_address: addresses.find_or_clone(bill_address))
49
+ end
50
+
51
+ def update_ship_address_by(ship_address)
52
+ update_attributes(ship_address: addresses.find_or_clone(ship_address))
53
+ end
54
+
55
+ def signed_in?
56
+ !new_record?
57
+ end
58
+
59
+ def not_signed_in?
60
+ !signed_in?
61
+ end
62
+
63
+ # TODO: Add a test case
64
+ def reload(*_)
65
+ super.tap do
66
+ @cart_items = nil
67
+ @incomplete_order = nil
68
+ end
69
+ end
70
+
71
+ def cart_items
72
+ incomplete_order.order_items
73
+ end
74
+
75
+ def incomplete_order
76
+ @incomplete_order = nil if @incomplete_order.try(:completed?)
77
+ @incomplete_order ||= find_incomplete_order || initialize_incomplete_order
78
+ end
79
+
80
+ def after_set_user
81
+ return unless current_guest_token
82
+
83
+ guest_order = Comable::Order.incomplete.preload(:order_items).where(guest_token: current_guest_token).first
84
+ return unless guest_order
85
+
86
+ incomplete_order.inherit!(guest_order)
87
+ inherit_cart_items(guest_order)
88
+ end
89
+
90
+ def human_id
91
+ "##{id}"
92
+ end
93
+
94
+ private
95
+
96
+ def current_guest_token
97
+ @cookies.signed[:guest_token] if @cookies
98
+ end
99
+
100
+ def initialize_incomplete_order
101
+ order = Comable::Order.create(incomplete_order_attributes)
102
+ @cookies.permanent.signed[:guest_token] = order.guest_token if @cookies
103
+ # enable preload
104
+ find_incomplete_order
105
+ end
106
+
107
+ def incomplete_order_attributes
108
+ {
109
+ user_id: id,
110
+ email: email
111
+ }
112
+ end
113
+
114
+ def find_incomplete_order
115
+ guest_token ||= current_guest_token unless signed_in?
116
+ Comable::Order
117
+ .incomplete
118
+ .preload(:order_items)
119
+ .where(guest_token: guest_token)
120
+ .by_user(self)
121
+ .first
122
+ end
123
+
124
+ def inherit_cart_items(guest_order)
125
+ guest_order.order_items.each do |order_item|
126
+ move_cart_item(order_item)
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,94 @@
1
+ module Comable
2
+ module CartOwner
3
+ def add_cart_item(obj, quantity: 1)
4
+ process_cart_item(obj) do |stock|
5
+ add_stock_to_cart(stock, quantity)
6
+ end
7
+ end
8
+
9
+ def remove_cart_item(obj, quantity: -1)
10
+ add_cart_item(obj, quantity: quantity)
11
+ end
12
+
13
+ def reset_cart_item(obj, quantity: 0)
14
+ process_cart_item(obj) do |stock|
15
+ reset_stock_from_cart(stock, quantity)
16
+ end
17
+ end
18
+
19
+ def move_cart_item(cart_item)
20
+ add_cart_item(cart_item.stock, quantity: cart_item.quantity) && cart_item.destroy
21
+ end
22
+
23
+ def cart_items
24
+ fail 'You should implement cart_items method.'
25
+ end
26
+
27
+ def cart
28
+ Cart.new(cart_items)
29
+ end
30
+
31
+ class Cart < Array
32
+ def price
33
+ sum(&:current_subtotal_price)
34
+ end
35
+
36
+ def count
37
+ sum(&:quantity)
38
+ end
39
+
40
+ alias_method :size, :count
41
+
42
+ # TODO: Refactoring
43
+ def errors
44
+ ActiveModel::Errors.new(self).tap do |obj|
45
+ map(&:errors).map(&:full_messages).flatten.each do |full_message|
46
+ obj[:base] << full_message
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def process_cart_item(obj)
55
+ case obj
56
+ when Comable::Product
57
+ yield obj.stocks.first
58
+ when Comable::Stock
59
+ yield obj
60
+ when Array
61
+ obj.map { |item| yield item }
62
+ else
63
+ fail
64
+ end
65
+ end
66
+
67
+ def add_stock_to_cart(stock, quantity)
68
+ cart_item = find_cart_item_by(stock)
69
+ if cart_item
70
+ cart_item.quantity += quantity
71
+ (cart_item.quantity > 0) ? cart_item.save : cart_item.destroy
72
+ else
73
+ cart_items.build(stock_id: stock.id, quantity: quantity).save
74
+ end
75
+ end
76
+
77
+ def reset_stock_from_cart(stock, quantity)
78
+ cart_item = find_cart_item_by(stock)
79
+ if quantity > 0
80
+ return add_stock_to_cart(stock, quantity) unless cart_item
81
+ cart_item.update_attributes(quantity: quantity)
82
+ else
83
+ return false unless cart_item
84
+ cart_items.destroy(cart_item)
85
+ end
86
+ end
87
+
88
+ def find_cart_item_by(stock)
89
+ # TODO: Refactoring
90
+ fail unless stock.is_a?(Comable::Stock)
91
+ cart_items.find { |cart_item| cart_item.stock.id == stock.id }
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,85 @@
1
+ module Comable
2
+ module Checkout
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ state_machine initial: :cart do
7
+ state :cart
8
+ state :orderer
9
+ state :delivery
10
+ state :shipment
11
+ state :payment
12
+ state :confirm
13
+ state :completed
14
+ state :canceled
15
+ state :returned
16
+ state :resumed
17
+
18
+ event :next_state do
19
+ transition :cart => :orderer, if: :orderer_required?
20
+ transition [:cart, :orderer] => :delivery, if: :delivery_required?
21
+ transition [:cart, :orderer, :delivery] => :shipment, if: :shipment_required?
22
+ transition [:cart, :orderer, :delivery, :shipment] => :payment, if: :payment_required?
23
+ transition all - [:confirm, :completed] => :confirm
24
+ transition :confirm => :completed
25
+ end
26
+
27
+ event :cancel do
28
+ transition to: :canceled, from: [:completed, :resumed], if: :allow_cancel?
29
+ end
30
+
31
+ event :return do
32
+ transition to: :returned, from: [:completed, :resumed], if: :allow_return?
33
+ end
34
+
35
+ event :resume do
36
+ transition to: :resumed, from: :canceled
37
+ end
38
+
39
+ before_transition to: :completed, do: :complete!
40
+ after_transition to: :canceled, do: :restock!
41
+ after_transition to: :resumed, do: :unstock!
42
+ end
43
+ end
44
+
45
+ module ClassMethods
46
+ def state_names
47
+ state_machine.states.keys
48
+ end
49
+ end
50
+
51
+ def stated?(target_state)
52
+ target_state_index = self.class.state_names.index(target_state.to_sym)
53
+ current_state_index = self.class.state_names.index(state_name)
54
+ target_state_index < current_state_index
55
+ end
56
+
57
+ def orderer_required?
58
+ bill_address.nil? || bill_address.new_record?
59
+ end
60
+
61
+ def delivery_required?
62
+ ship_address.nil? || ship_address.new_record?
63
+ end
64
+
65
+ def payment_required?
66
+ Comable::PaymentMethod.exists? && payment.nil?
67
+ end
68
+
69
+ def shipment_required?
70
+ Comable::ShipmentMethod.activated.exists? && shipment.nil?
71
+ end
72
+
73
+ def allow_cancel?
74
+ # TODO: Implement shipments
75
+ # !shipments.exists?
76
+ true
77
+ end
78
+
79
+ def allow_return?
80
+ # TODO: Implement shipments
81
+ # shipments.exists?
82
+ false
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,67 @@
1
+ module Comable
2
+ module Importable
3
+ extend ActiveSupport::Concern
4
+
5
+ class Exception < StandardError
6
+ end
7
+
8
+ class UnknownFileType < Comable::Importable::Exception
9
+ end
10
+
11
+ class RecordInvalid < Comable::Importable::Exception
12
+ end
13
+
14
+ # from http://railscasts.com/episodes/396-importing-csv-and-excel
15
+ module ClassMethods
16
+ def import_from(file, primary_key: :code)
17
+ spreadsheet = open_spreadsheet(file)
18
+ read_spreadsheet(spreadsheet) do |header, row|
19
+ attributes = attributes_from_header_and_row(header, row)
20
+ record = find_by(primary_key => attributes[primary_key]) || new
21
+ begin
22
+ record.update_attributes!(attributes)
23
+ rescue ActiveRecord::RecordInvalid => e
24
+ raise RecordInvalid, "#{record.class.human_attribute_name(primary_key)} \"#{record.send(primary_key)}\": #{e.message}"
25
+ end
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def open_spreadsheet(file)
32
+ case File.extname(file.original_filename)
33
+ when '.csv' then Roo::CSV.new(file.path)
34
+ when '.xls' then Roo::Excel.new(file.path, nil, :ignore)
35
+ when '.xlsx' then Roo::Excelx.new(file.path, nil, :ignore)
36
+ else fail UnknownFileType, Comable.t('admin.unknown_file_type', filename: file.original_filename)
37
+ end
38
+ end
39
+
40
+ def read_spreadsheet(spreadsheet)
41
+ header = spreadsheet.row(1)
42
+ (2..spreadsheet.last_row).each do |i|
43
+ yield header, spreadsheet.row(i)
44
+ end
45
+ end
46
+
47
+ def attributes_from_header_and_row(header, row)
48
+ human_attributes = Hash[[header, row].transpose].to_hash
49
+ human_attributes_to_attributes(human_attributes)
50
+ end
51
+
52
+ def human_attributes_to_attributes(human_attributes)
53
+ comma_column_names.each.with_object({}) do |(key, _value), result|
54
+ human_key = Comma::HeaderExtractor.value_humanizer.call(key.to_sym, self)
55
+ result[key.to_sym] = human_attributes[human_key] if human_attributes[human_key]
56
+ result
57
+ end
58
+ end
59
+
60
+ def comma_column_names(style = :default)
61
+ header_extractor_class = Comma::HeaderExtractor.dup
62
+ header_extractor_class.value_humanizer = -> (value, _model_class) { value.to_s }
63
+ extract_with(header_extractor_class, style)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,12 @@
1
+ module Comable
2
+ module Liquidable
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ # from: http://www.codedisqus.com/0SHjkUjjgW/how-can-i-expose-all-available-liquid-methods-for-a-model.html
7
+ def available_liquid_methods
8
+ self::LiquidDropClass.public_instance_methods - Liquid::Drop.public_instance_methods
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,41 @@
1
+ module Comable
2
+ class Product
3
+ module Search
4
+ extend ActiveSupport::Concern
5
+
6
+ SEARCH_COLUMNS = %i( code name caption )
7
+
8
+ module ClassMethods
9
+ def search(query)
10
+ keywords = parse_to_keywords(query)
11
+ return (Rails::VERSION::MAJOR == 3) ? scoped : all if keywords.empty?
12
+ where(keywords_to_arel(keywords))
13
+ end
14
+
15
+ private
16
+
17
+ def keywords_to_arel(keywords)
18
+ keywords.inject(nil) do |arel_chain, keyword|
19
+ arel = keyword_to_arel(keyword)
20
+ arel_chain ? arel_chain.and(arel) : arel
21
+ end
22
+ end
23
+
24
+ def keyword_to_arel(keyword)
25
+ SEARCH_COLUMNS.inject(nil) do |arel_chain, column|
26
+ arel = arel_table[column].matches("%#{keyword}%")
27
+ arel_chain ? arel_chain.or(arel) : arel
28
+ end
29
+ end
30
+
31
+ def parse_to_keywords(query)
32
+ return [] if query.blank?
33
+ query
34
+ .delete('%')
35
+ .tr(' ', ' ')
36
+ .split(' ')
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end