comable-core 0.6.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 (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