spree_core 2.0.13 → 2.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 (149) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/spree/base_controller.rb +3 -0
  3. data/app/helpers/spree/base_helper.rb +6 -16
  4. data/app/helpers/spree/products_helper.rb +3 -8
  5. data/app/helpers/spree/taxons_helper.rb +1 -1
  6. data/app/mailers/spree/base_mailer.rb +0 -5
  7. data/app/models/spree/ability.rb +10 -7
  8. data/app/models/spree/address.rb +7 -17
  9. data/app/models/spree/adjustment.rb +15 -11
  10. data/app/models/spree/app_configuration.rb +0 -5
  11. data/app/models/spree/billing_integration.rb +0 -1
  12. data/app/models/spree/calculator/flat_percent_item_total.rb +1 -3
  13. data/app/models/spree/calculator/flat_rate.rb +2 -4
  14. data/app/models/spree/calculator/flexi_rate.rb +6 -9
  15. data/app/models/spree/calculator/per_item.rb +2 -4
  16. data/app/models/spree/calculator/percent_per_item.rb +1 -3
  17. data/app/models/spree/calculator/price_sack.rb +4 -9
  18. data/app/models/spree/calculator/shipping/flat_percent_item_total.rb +1 -2
  19. data/app/models/spree/calculator/shipping/flat_rate.rb +2 -4
  20. data/app/models/spree/calculator/shipping/flexi_rate.rb +4 -9
  21. data/app/models/spree/calculator/shipping/per_item.rb +2 -3
  22. data/app/models/spree/calculator/shipping/price_sack.rb +4 -9
  23. data/app/models/spree/classification.rb +0 -3
  24. data/app/models/spree/country.rb +1 -3
  25. data/app/models/spree/credit_card.rb +37 -38
  26. data/app/models/spree/gateway/bogus_simple.rb +0 -8
  27. data/app/models/spree/gateway.rb +1 -3
  28. data/app/models/spree/image.rb +1 -3
  29. data/app/models/spree/inventory_unit.rb +5 -8
  30. data/app/models/spree/legacy_user.rb +0 -4
  31. data/app/models/spree/line_item.rb +2 -15
  32. data/app/models/spree/option_type.rb +2 -5
  33. data/app/models/spree/option_value.rb +1 -3
  34. data/app/models/spree/order/checkout.rb +4 -13
  35. data/app/models/spree/order.rb +47 -99
  36. data/app/models/spree/order_contents.rb +4 -7
  37. data/app/models/spree/order_inventory.rb +4 -8
  38. data/app/models/spree/order_updater.rb +13 -12
  39. data/app/models/spree/payment/processing.rb +12 -19
  40. data/app/models/spree/payment.rb +17 -30
  41. data/app/models/spree/payment_method.rb +2 -3
  42. data/app/models/spree/preference.rb +1 -1
  43. data/app/models/spree/preferences/configuration.rb +1 -1
  44. data/app/models/spree/preferences/preferable.rb +1 -1
  45. data/app/models/spree/preferences/store.rb +1 -1
  46. data/app/models/spree/price.rb +0 -7
  47. data/app/models/spree/product/scopes.rb +16 -17
  48. data/app/models/spree/product.rb +27 -62
  49. data/app/models/spree/product_property.rb +3 -5
  50. data/app/models/spree/promotion/actions/create_adjustment.rb +9 -8
  51. data/app/models/spree/promotion/actions/create_line_items.rb +1 -2
  52. data/app/models/spree/promotion/rules/first_order.rb +1 -1
  53. data/app/models/spree/promotion/rules/item_total.rb +2 -4
  54. data/app/models/spree/promotion/rules/product.rb +2 -2
  55. data/app/models/spree/promotion/rules/user.rb +1 -3
  56. data/app/models/spree/promotion.rb +23 -24
  57. data/app/models/spree/promotion_action.rb +0 -2
  58. data/app/models/spree/promotion_action_line_item.rb +1 -3
  59. data/app/models/spree/promotion_rule.rb +0 -2
  60. data/app/models/spree/property.rb +2 -4
  61. data/app/models/spree/prototype.rb +0 -2
  62. data/app/models/spree/return_authorization.rb +6 -9
  63. data/app/models/spree/role.rb +0 -2
  64. data/app/models/spree/shipment.rb +19 -25
  65. data/app/models/spree/shipping_calculator.rb +0 -2
  66. data/app/models/spree/shipping_category.rb +0 -2
  67. data/app/models/spree/shipping_method.rb +6 -20
  68. data/app/models/spree/shipping_rate.rb +12 -10
  69. data/app/models/spree/state.rb +2 -4
  70. data/app/models/spree/stock/availability_validator.rb +2 -2
  71. data/app/models/spree/stock/estimator.rb +6 -20
  72. data/app/models/spree/stock/packer.rb +1 -1
  73. data/app/models/spree/stock/quantifier.rb +2 -3
  74. data/app/models/spree/stock/splitter/base.rb +1 -1
  75. data/app/models/spree/stock_item.rb +8 -18
  76. data/app/models/spree/stock_location.rb +2 -11
  77. data/app/models/spree/stock_movement.rb +2 -5
  78. data/app/models/spree/stock_transfer.rb +0 -2
  79. data/app/models/spree/tax_category.rb +0 -2
  80. data/app/models/spree/tax_rate.rb +12 -12
  81. data/app/models/spree/taxon.rb +1 -13
  82. data/app/models/spree/taxonomy.rb +3 -6
  83. data/app/models/spree/tracker.rb +0 -2
  84. data/app/models/spree/variant/scopes.rb +2 -2
  85. data/app/models/spree/variant.rb +13 -31
  86. data/app/models/spree/zone.rb +2 -7
  87. data/app/models/spree/zone_member.rb +0 -2
  88. data/app/views/spree/payments/_payment.html.erb +1 -3
  89. data/config/locales/en.yml +11 -26
  90. data/db/default/spree/countries.rb +230 -229
  91. data/db/default/spree/states.rb +57 -56
  92. data/db/default/spree/zones.rb +5 -5
  93. data/db/migrate/20130213191427_create_default_stock.rb +4 -7
  94. data/db/migrate/20130417120035_update_adjustment_states.rb +2 -2
  95. data/db/migrate/20130417123427_add_shipping_rates_to_shipments.rb +1 -1
  96. data/db/migrate/20130509115210_add_number_to_stock_transfer.rb +1 -1
  97. data/db/migrate/20130611054351_rename_shipping_methods_zones_to_spree_shipping_methods_zones.rb +0 -5
  98. data/db/migrate/20130611185927_add_user_id_index_to_spree_orders.rb +5 -0
  99. data/db/migrate/20130618041418_add_updated_at_to_spree_countries.rb +9 -0
  100. data/db/migrate/20130619012236_add_updated_at_to_spree_states.rb +9 -0
  101. data/db/migrate/20130802022321_migrate_tax_categories_to_line_items.rb +4 -5
  102. data/db/migrate/20130806145853_set_default_stock_location_on_shipments.rb +1 -1
  103. data/lib/generators/spree/dummy/dummy_generator.rb +3 -14
  104. data/lib/generators/spree/dummy/templates/rails/database.yml +0 -10
  105. data/lib/generators/spree/dummy/templates/rails/test.rb +2 -7
  106. data/lib/generators/spree/install/install_generator.rb +11 -8
  107. data/lib/spree/core/calculated_adjustments.rb +9 -8
  108. data/lib/spree/core/controller_helpers/auth.rb +2 -3
  109. data/lib/spree/core/controller_helpers/order.rb +8 -13
  110. data/lib/spree/core/controller_helpers/ssl.rb +13 -22
  111. data/lib/spree/core/controller_helpers/strong_parameters.rb +36 -0
  112. data/lib/spree/core/delegate_belongs_to.rb +0 -2
  113. data/lib/spree/core/engine.rb +1 -5
  114. data/lib/spree/core/ext/active_record.rb +2 -9
  115. data/lib/spree/core/permalinks.rb +1 -5
  116. data/lib/spree/core/product_duplicator.rb +2 -16
  117. data/lib/spree/core/product_filters.rb +37 -33
  118. data/lib/spree/core/search/base.rb +1 -1
  119. data/lib/spree/core/version.rb +1 -1
  120. data/lib/spree/core.rb +3 -31
  121. data/lib/spree/i18n.rb +0 -1
  122. data/lib/spree/money.rb +2 -177
  123. data/lib/spree/permitted_attributes.rb +95 -0
  124. data/lib/spree/promo/coupon_applicator.rb +4 -12
  125. data/lib/spree/testing_support/capybara_ext.rb +13 -17
  126. data/lib/spree/testing_support/common_rake.rb +1 -1
  127. data/lib/spree/testing_support/controller_requests.rb +3 -3
  128. data/lib/spree/testing_support/factories/credit_card_factory.rb +1 -1
  129. data/lib/spree/testing_support/factories/product_factory.rb +0 -4
  130. data/lib/spree/testing_support/factories/shipping_method_factory.rb +1 -3
  131. data/lib/spree/testing_support/factories/user_factory.rb +1 -1
  132. data/lib/spree/testing_support/factories/variant_factory.rb +0 -15
  133. data/lib/spree/testing_support/factories.rb +1 -1
  134. data/lib/spree/testing_support/order_walkthrough.rb +1 -1
  135. data/lib/tasks/core.rake +2 -2
  136. data/vendor/assets/javascripts/jquery.payment.js +497 -0
  137. metadata +166 -172
  138. data/app/views/spree/admin/shared/_report_order_criteria.html.erb +0 -17
  139. data/db/migrate/20130417120034_add_index_to_source_columns_on_adjustments.rb +0 -5
  140. data/db/migrate/20130830001033_add_shipping_category_to_shipping_methods_and_products.rb +0 -15
  141. data/db/migrate/20130830001159_migrate_old_shipping_calculators.rb +0 -19
  142. data/db/migrate/20130909115621_change_states_required_for_countries.rb +0 -9
  143. data/db/migrate/20131001013410_remove_unused_credit_card_fields.rb +0 -12
  144. data/db/migrate/20131026154747_add_track_inventory_to_variant.rb +0 -5
  145. data/db/migrate/20131113035136_add_channel_to_spree_orders.rb +0 -5
  146. data/db/migrate/20140120160805_add_index_to_variant_id_and_currency_on_prices.rb +0 -5
  147. data/db/migrate/20140205181631_default_variant_weight_to_zero.rb +0 -11
  148. data/db/migrate/20140415041315_add_user_id_created_by_id_index_to_order.rb +0 -5
  149. data/lib/spree/core/preference_rescue.rb +0 -25
@@ -8,14 +8,14 @@ module Spree
8
8
 
9
9
  # Get current line item for variant if exists
10
10
  # Add variant qty to line_item
11
- def add(variant, quantity=1, currency=nil, shipment=nil)
11
+ def add(variant, quantity = 1, currency = nil, shipment = nil)
12
12
  line_item = order.find_line_item_by_variant(variant)
13
13
  add_to_line_item(line_item, variant, quantity, currency, shipment)
14
14
  end
15
15
 
16
16
  # Get current line item for variant
17
17
  # Remove variant qty from line_item
18
- def remove(variant, quantity=1, shipment=nil)
18
+ def remove(variant, quantity = 1, shipment = nil)
19
19
  line_item = order.find_line_item_by_variant(variant)
20
20
 
21
21
  unless line_item
@@ -32,21 +32,18 @@ module Spree
32
32
  line_item.target_shipment = shipment
33
33
  line_item.quantity += quantity.to_i
34
34
  line_item.currency = currency unless currency.nil?
35
- line_item.save
36
35
  else
37
- line_item = LineItem.new(quantity: quantity)
36
+ line_item = order.line_items.new(quantity: quantity, variant: variant)
38
37
  line_item.target_shipment = shipment
39
- line_item.variant = variant
40
38
  if currency
41
39
  line_item.currency = currency unless currency.nil?
42
40
  line_item.price = variant.price_in(currency).amount
43
41
  else
44
42
  line_item.price = variant.price
45
43
  end
46
- order.line_items << line_item
47
- line_item
48
44
  end
49
45
 
46
+ line_item.save
50
47
  order.reload
51
48
  line_item
52
49
  end
@@ -32,7 +32,7 @@ module Spree
32
32
  end
33
33
 
34
34
  def inventory_units_for(variant)
35
- units = order.shipments.collect{|s| s.inventory_units.all}.flatten
35
+ units = order.shipments.collect{|s| s.inventory_units.to_a}.flatten
36
36
  units.group_by(&:variant_id)[variant.id] || []
37
37
  end
38
38
 
@@ -65,14 +65,10 @@ module Spree
65
65
  end
66
66
 
67
67
  def add_to_shipment(shipment, variant, quantity)
68
- if variant.should_track_inventory?
69
- on_hand, back_order = shipment.stock_location.fill_status(variant, quantity)
68
+ on_hand, back_order = shipment.stock_location.fill_status(variant, quantity)
70
69
 
71
- on_hand.times { shipment.set_up_inventory('on_hand', variant, order) }
72
- back_order.times { shipment.set_up_inventory('backordered', variant, order) }
73
- else
74
- quantity.times { shipment.set_up_inventory('on_hand', variant, order) }
75
- end
70
+ on_hand.times { shipment.set_up_inventory('on_hand', variant, order) }
71
+ back_order.times { shipment.set_up_inventory('backordered', variant, order) }
76
72
 
77
73
  # adding to this shipment, and removing from stock_location
78
74
  if order.completed?
@@ -25,7 +25,8 @@ module Spree
25
25
  update_shipment_state
26
26
  end
27
27
 
28
- update_adjustments
28
+ update_promotion_adjustments
29
+ update_shipping_adjustments
29
30
  # update totals a second time in case updated adjustments have an effect on the total
30
31
  update_totals
31
32
 
@@ -101,16 +102,10 @@ module Spree
101
102
  # The +payment_state+ value helps with reporting, etc. since it provides a quick and easy way to locate Orders needing attention.
102
103
  def update_payment_state
103
104
 
104
- # line_item are empty when user empties cart
105
+ #line_item are empty when user empties cart
105
106
  if line_items.empty? || round_money(order.payment_total) < round_money(order.total)
106
- if payments.present?
107
- if payments.last.state == 'failed'
108
- order.payment_state = 'failed'
109
- elsif payments.last.state == 'completed'
110
- order.payment_state = 'credit_owed'
111
- else
112
- order.payment_state = 'balance_due'
113
- end
107
+ if payments.present? && payments.last.state == 'failed'
108
+ order.payment_state = 'failed'
114
109
  else
115
110
  order.payment_state = 'balance_due'
116
111
  end
@@ -130,11 +125,17 @@ module Spree
130
125
  #
131
126
  # Adjustments will check if they are still eligible. Ineligible adjustments
132
127
  # are preserved but not counted towards adjustment_total.
133
- def update_adjustments
134
- order.adjustments.reload.each { |adjustment| adjustment.update!(order) }
128
+ def update_promotion_adjustments
129
+ order.adjustments.reload.promotion.each { |adjustment| adjustment.update!(order) }
135
130
  choose_best_promotion_adjustment
136
131
  end
137
132
 
133
+ # Shipping adjustments don't receive order on update! because they calculated
134
+ # over a shipping / package object rather than an order object
135
+ def update_shipping_adjustments
136
+ order.adjustments.reload.shipping.each { |adjustment| adjustment.update! }
137
+ end
138
+
138
139
  private
139
140
 
140
141
  # Picks one (and only one) promotion to be eligible for this order
@@ -83,37 +83,30 @@ module Spree
83
83
 
84
84
  credit_amount ||= credit_allowed >= order.outstanding_balance.abs ? order.outstanding_balance.abs : credit_allowed.abs
85
85
  credit_amount = credit_amount.to_f
86
- credit_cents = Spree::Money.new(credit_amount, currency: currency).money.cents
87
86
 
88
87
  if payment_method.payment_profiles_supported?
89
- response = payment_method.credit(credit_cents, source, response_code, gateway_options)
88
+ response = payment_method.credit((credit_amount * 100).round, source, response_code, gateway_options)
90
89
  else
91
- response = payment_method.credit(credit_cents, response_code, gateway_options)
90
+ response = payment_method.credit((credit_amount * 100).round, response_code, gateway_options)
92
91
  end
93
92
 
94
93
  record_response(response)
95
94
 
96
95
  if response.success?
97
- self.class.create({ :order => order,
98
- :source => self,
99
- :payment_method => payment_method,
100
- :amount => credit_amount.abs * -1,
101
- :response_code => response.authorization,
102
- :state => 'completed' }, :without_protection => true)
96
+ self.class.create(
97
+ :order => order,
98
+ :source => self,
99
+ :payment_method => payment_method,
100
+ :amount => credit_amount.abs * -1,
101
+ :response_code => response.authorization,
102
+ :state => 'completed'
103
+ )
103
104
  else
104
105
  gateway_error(response)
105
106
  end
106
107
  end
107
108
  end
108
109
 
109
- def cancel!
110
- if payment_method.respond_to?(:cancel)
111
- payment_method.cancel(response_code)
112
- else
113
- credit!
114
- end
115
- end
116
-
117
110
  def partial_credit(amount)
118
111
  return if amount > credit_allowed
119
112
  started_processing!
@@ -148,7 +141,7 @@ module Spree
148
141
  protect_from_connection_error do
149
142
  check_environment
150
143
 
151
- response = payment_method.send(action, money.money.cents,
144
+ response = payment_method.send(action, (amount * 100).round,
152
145
  source,
153
146
  gateway_options)
154
147
  handle_response(response, success_state, :failure)
@@ -176,7 +169,7 @@ module Spree
176
169
  end
177
170
 
178
171
  def record_response(response)
179
- log_entries.create({:details => response.to_yaml}, :without_protection => true)
172
+ log_entries.create(:details => response.to_yaml)
180
173
  end
181
174
 
182
175
  def protect_from_connection_error
@@ -4,15 +4,16 @@ module Spree
4
4
 
5
5
  IDENTIFIER_CHARS = (('A'..'Z').to_a + ('0'..'9').to_a - %w(0 1 I O)).freeze
6
6
 
7
- belongs_to :order, class_name: 'Spree::Order', touch: true
7
+ belongs_to :order, class_name: 'Spree::Order'
8
8
  belongs_to :source, polymorphic: true
9
9
  belongs_to :payment_method, class_name: 'Spree::PaymentMethod'
10
10
 
11
- has_many :offsets, class_name: "Spree::Payment", foreign_key: :source_id, conditions: "source_type = 'Spree::Payment' AND amount < 0 AND state = 'completed'"
11
+ has_many :offsets, -> { where("source_type = 'Spree::Payment' AND amount < 0 AND state = 'completed'") },
12
+ class_name: "Spree::Payment", foreign_key: :source_id
12
13
  has_many :log_entries, as: :source
13
14
 
14
15
  before_validation :validate_source
15
- before_create :set_unique_identifier
16
+ before_save :set_unique_identifier
16
17
 
17
18
  after_save :create_payment_profile, if: :profiles_supported?
18
19
 
@@ -24,49 +25,45 @@ module Spree
24
25
  attr_accessor :source_attributes
25
26
  after_initialize :build_source
26
27
 
27
- attr_accessible :amount, :payment_method_id, :source_attributes
28
-
29
28
  scope :from_credit_card, -> { where(source_type: 'Spree::CreditCard') }
30
29
  scope :with_state, ->(s) { where(state: s.to_s) }
31
- scope :completed, with_state('completed')
32
- scope :pending, with_state('pending')
33
- scope :failed, with_state('failed')
34
- scope :valid, where("#{quoted_table_name}.state NOT IN (?)", %w(failed invalid))
30
+ scope :completed, -> { with_state('completed') }
31
+ scope :pending, -> { with_state('pending') }
32
+ scope :failed, -> { with_state('failed') }
33
+ scope :valid, -> { where('state NOT IN (?)', %w(failed invalid)) }
35
34
 
36
35
  after_rollback :persist_invalid
37
36
 
38
- validates :amount, numericality: true
39
-
40
37
  def persist_invalid
41
38
  return unless ['failed', 'invalid'].include?(state)
42
39
  state_will_change!
43
- save
40
+ save
44
41
  end
45
42
 
46
43
  # order state machine (see http://github.com/pluginaweek/state_machine/tree/master for details)
47
- state_machine initial: 'checkout' do
44
+ state_machine initial: :checkout do
48
45
  # With card payments, happens before purchase or authorization happens
49
46
  event :started_processing do
50
- transition from: ['checkout', 'pending', 'completed', 'processing'], to: 'processing'
47
+ transition from: [:checkout, :pending, :completed, :processing], to: :processing
51
48
  end
52
49
  # When processing during checkout fails
53
50
  event :failure do
54
- transition from: ['pending', 'processing'], to: 'failed'
51
+ transition from: [:pending, :processing], to: :failed
55
52
  end
56
53
  # With card payments this represents authorizing the payment
57
54
  event :pend do
58
- transition from: ['checkout', 'processing'], to: 'pending'
55
+ transition from: [:checkout, :processing], to: :pending
59
56
  end
60
57
  # With card payments this represents completing a purchase or capture transaction
61
58
  event :complete do
62
- transition from: ['processing', 'pending', 'checkout'], to: 'completed'
59
+ transition from: [:processing, :pending, :checkout], to: :completed
63
60
  end
64
61
  event :void do
65
- transition from: ['pending', 'completed', 'checkout'], to: 'void'
62
+ transition from: [:pending, :completed, :checkout], to: :void
66
63
  end
67
64
  # when the card brand isnt supported
68
65
  event :invalidate do
69
- transition from: ['checkout'], to: 'invalid'
66
+ transition from: [:checkout], to: :invalid
70
67
  end
71
68
  end
72
69
 
@@ -79,22 +76,12 @@ module Spree
79
76
  end
80
77
  alias display_amount money
81
78
 
82
- def amount=(amount)
83
- self[:amount] =
84
- case amount
85
- when String
86
- separator = I18n.t('number.currency.format.separator')
87
- number = amount.delete("^0-9-#{separator}").tr(separator, '.')
88
- number.to_d if number.present?
89
- end || amount
90
- end
91
-
92
79
  def offsets_total
93
80
  offsets.pluck(:amount).sum
94
81
  end
95
82
 
96
83
  def credit_allowed
97
- amount - offsets_total.abs
84
+ amount - offsets_total
98
85
  end
99
86
 
100
87
  def can_credit?
@@ -2,11 +2,10 @@ module Spree
2
2
  class PaymentMethod < ActiveRecord::Base
3
3
  acts_as_paranoid
4
4
  DISPLAY = [:both, :front_end, :back_end]
5
- default_scope where(deleted_at: nil)
5
+ default_scope -> { where(deleted_at: nil) }
6
6
 
7
7
  scope :production, -> { where(environment: 'production') }
8
8
 
9
- attr_accessible :name, :description, :environment, :display_on, :active
10
9
  validates :name, presence: true
11
10
 
12
11
  def self.providers
@@ -41,7 +40,7 @@ module Spree
41
40
  end
42
41
 
43
42
  def self.find_with_destroyed *args
44
- self.with_exclusive_scope { find(*args) }
43
+ unscoped { find(*args) }
45
44
  end
46
45
 
47
46
  def payment_profiles_supported?
@@ -1,5 +1,5 @@
1
1
  class Spree::Preference < ActiveRecord::Base
2
- attr_accessible :key, :value_type, :value
2
+ serialize :value
3
3
 
4
4
  validates :key, presence: true
5
5
  validates :value_type, presence: true
@@ -29,7 +29,7 @@ module Spree::Preferences
29
29
  end
30
30
 
31
31
  def preference_cache_key(name)
32
- [self.class.name, name].join('::').underscore
32
+ [ENV['RAILS_CACHE_ID'], self.class.name, name].flatten.join('::').underscore
33
33
  end
34
34
 
35
35
  def reset
@@ -78,7 +78,7 @@ module Spree::Preferences::Preferable
78
78
 
79
79
  def preference_cache_key(name)
80
80
  return unless id
81
- [self.class.name, name, id].join('::').underscore
81
+ [ENV["RAILS_CACHE_ID"], self.class.name, name, id].join('::').underscore
82
82
  end
83
83
 
84
84
  def save_pending_preferences
@@ -86,7 +86,7 @@ module Spree::Preferences
86
86
  end
87
87
 
88
88
  def should_persist?
89
- @persistence and Spree::Preference.table_exists?
89
+ @persistence && Spree::Preference.connected? && Spree::Preference.table_exists?
90
90
  end
91
91
 
92
92
  end
@@ -5,8 +5,6 @@ module Spree
5
5
  validate :check_price
6
6
  validates :amount, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
7
7
 
8
- attr_accessible :variant_id, :currency, :amount
9
-
10
8
  def display_amount
11
9
  money
12
10
  end
@@ -24,11 +22,6 @@ module Spree
24
22
  self[:amount] = parse_price(price)
25
23
  end
26
24
 
27
- # Remove variant default_scope `deleted_at: nil`
28
- def variant
29
- Spree::Variant.unscoped { super }
30
- end
31
-
32
25
  private
33
26
  def check_price
34
27
  raise "Price must belong to a variant" if variant.nil?
@@ -24,7 +24,7 @@ module Spree
24
24
  next if name.to_s.include?("master_price")
25
25
  parts = name.to_s.match(/(.*)_by_(.*)/)
26
26
  order_text = "#{Product.quoted_table_name}.#{parts[2]} #{parts[1] == 'ascend' ? "ASC" : "DESC"}"
27
- self.scope(name.to_s, order(order_text))
27
+ self.scope(name.to_s, -> { relation.order(order_text) })
28
28
  end
29
29
  end
30
30
 
@@ -68,11 +68,14 @@ module Spree
68
68
  #
69
69
  # SELECT COUNT(*) ...
70
70
  add_search_scope :in_taxon do |taxon|
71
- select("spree_products.id, spree_products.*").
72
- where(id: Classification.select('spree_products_taxons.product_id').
73
- joins(:taxon).
74
- where(Taxon.table_name => { :id => taxon.self_and_descendants.pluck(:id) })
75
- )
71
+ if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
72
+ scope = select("DISTINCT ON (spree_products.id) spree_products.*")
73
+ else
74
+ scope = select("DISTINCT(spree_products.id), spree_products.*")
75
+ end
76
+
77
+ scope.joins(:taxons).
78
+ where(Taxon.table_name => { :id => taxon.self_and_descendants.map(&:id) })
76
79
  end
77
80
 
78
81
  # This scope selects products in all taxons AND all its descendants
@@ -124,20 +127,20 @@ module Spree
124
127
  add_search_scope :with_option_value do |option, value|
125
128
  option_values = OptionValue.table_name
126
129
  option_type_id = case option
127
- when String then OptionType.find_by_name(option) || option.to_i
130
+ when String then OptionType.find_by(name: option) || option.to_i
128
131
  when OptionType then option.id
129
132
  else option.to_i
130
133
  end
131
134
 
132
135
  conditions = "#{option_values}.name = ? AND #{option_values}.option_type_id = ?", value, option_type_id
133
- group("spree_products.id").joins(:variants_including_master => :option_values).where(conditions)
136
+ group('spree_products.id').joins(variants_including_master: :option_values).where(conditions)
134
137
  end
135
138
 
136
139
  # Finds all products which have either:
137
140
  # 1) have an option value with the name matching the one given
138
141
  # 2) have a product property with a value matching the one given
139
142
  add_search_scope :with do |value|
140
- includes(:variants_including_master => :option_values).
143
+ includes(variants_including_master: :option_values).
141
144
  includes(:product_properties).
142
145
  where("#{OptionValue.table_name}.name = ? OR #{ProductProperty.table_name}.value = ?", value, value)
143
146
  end
@@ -160,7 +163,7 @@ module Spree
160
163
  # Finds all products that have the ids matching the given collection of ids.
161
164
  # Alternatively, you could use find(collection_of_ids), but that would raise an exception if one product couldn't be found
162
165
  add_search_scope :with_ids do |*ids|
163
- where(:id => ids)
166
+ where(id: ids)
164
167
  end
165
168
 
166
169
  # Sorts products from most popular (popularity is extracted from how many
@@ -195,11 +198,7 @@ module Spree
195
198
 
196
199
  # Can't use add_search_scope for this as it needs a default argument
197
200
  def self.available(available_on = nil, currency = nil)
198
- scope = joins(:master => :prices).where("#{Product.quoted_table_name}.available_on <= ?", available_on || Time.now)
199
- unless Spree::Config.show_products_without_price
200
- scope = scope.where('spree_prices.currency' => currency || Spree::Config[:currency]).where('spree_prices.amount IS NOT NULL')
201
- end
202
- scope
201
+ joins(:master => :prices).where("#{Product.quoted_table_name}.available_on <= ?", available_on || Time.now)
203
202
  end
204
203
  search_scopes << :available
205
204
 
@@ -249,10 +248,10 @@ module Spree
249
248
  taxons = Taxon.table_name
250
249
  ids_or_records_or_names.flatten.map { |t|
251
250
  case t
252
- when Integer then Taxon.find_by_id(t)
251
+ when Integer then Taxon.find_by(id: t)
253
252
  when ActiveRecord::Base then t
254
253
  when String
255
- Taxon.find_by_name(t) ||
254
+ Taxon.find_by(name: t) ||
256
255
  Taxon.where("#{taxons}.permalink LIKE ? OR #{taxons}.permalink = ?", "%/#{t}/", "#{t}/").first
257
256
  end
258
257
  }.compact.flatten.uniq
@@ -34,22 +34,22 @@ module Spree
34
34
  belongs_to :shipping_category, class_name: 'Spree::ShippingCategory'
35
35
 
36
36
  has_one :master,
37
+ -> { where is_master: true },
37
38
  class_name: 'Spree::Variant',
38
- conditions: { is_master: true },
39
39
  dependent: :destroy
40
40
 
41
41
  has_many :variants,
42
- class_name: 'Spree::Variant',
43
- conditions: { is_master: false, deleted_at: nil },
44
- order: "#{::Spree::Variant.quoted_table_name}.position ASC"
42
+ -> { where(is_master: false).order("#{::Spree::Variant.quoted_table_name}.position ASC") },
43
+ class_name: 'Spree::Variant'
45
44
 
46
45
  has_many :variants_including_master,
46
+ -> { order("#{::Spree::Variant.quoted_table_name}.position ASC") },
47
47
  class_name: 'Spree::Variant',
48
- dependent: :destroy,
49
- order: "#{::Spree::Variant.quoted_table_name}.position ASC"
48
+ dependent: :destroy
49
+
50
+ has_many :prices, -> { order('spree_variants.position, spree_variants.id, currency') }, through: :variants
50
51
 
51
- has_many :prices, through: :variants, order: "#{::Spree::Variant.quoted_table_name}.position, #{::Spree::Variant.quoted_table_name}.id, #{::Spree::Price.quoted_table_name}.currency"
52
- has_many :stock_items, through: :variants_including_master
52
+ has_many :stock_items, through: :variants
53
53
 
54
54
  delegate_belongs_to :master, :sku, :price, :currency, :display_amount, :display_price, :weight, :height, :width, :depth, :is_master, :has_default_price?, :cost_currency, :price_in, :amount_in
55
55
  delegate_belongs_to :master, :cost_price if Variant.table_exists? && Variant.column_names.include?('cost_price')
@@ -62,7 +62,7 @@ module Spree
62
62
  delegate :images, to: :master, prefix: true
63
63
  alias_method :images, :master_images
64
64
 
65
- has_many :variant_images, source: :images, through: :variants_including_master, order: :position
65
+ has_many :variant_images, -> { order(:position) }, source: :images, through: :variants_including_master
66
66
 
67
67
  accepts_nested_attributes_for :variants, allow_destroy: true
68
68
 
@@ -73,31 +73,6 @@ module Spree
73
73
 
74
74
  attr_accessor :option_values_hash
75
75
 
76
- attr_accessible :available_on,
77
- :cost_currency,
78
- :deleted_at,
79
- :depth,
80
- :description,
81
- :height,
82
- :meta_description,
83
- :meta_keywords,
84
- :name,
85
- :option_type_ids,
86
- :option_values_hash,
87
- :permalink,
88
- :price,
89
- :product_properties_attributes,
90
- :prototype_id,
91
- :shipping_category_id,
92
- :sku,
93
- :tax_category_id,
94
- :taxon_ids,
95
- :weight,
96
- :width,
97
- :variants_attributes
98
-
99
- attr_accessible :cost_price if Variant.table_exists? && Variant.column_names.include?('cost_price')
100
-
101
76
  accepts_nested_attributes_for :product_properties, allow_destroy: true, reject_if: lambda { |pp| pp[:property_name].blank? }
102
77
 
103
78
  make_permalink order: :name
@@ -108,11 +83,6 @@ module Spree
108
83
 
109
84
  before_destroy :punch_permalink
110
85
 
111
- def variants_with_only_master
112
- ActiveSupport::Deprecation.warn("[SPREE] Spree::Product#variants_with_only_master will be deprecated in Spree 1.3. Please use Spree::Product#master instead.")
113
- master
114
- end
115
-
116
86
  def to_param
117
87
  permalink.present? ? permalink : (permalink_was || name.to_s.to_url)
118
88
  end
@@ -141,7 +111,7 @@ module Spree
141
111
  return if option_values_hash.nil?
142
112
  option_values_hash.keys.map(&:to_i).each do |id|
143
113
  self.option_type_ids << id unless option_type_ids.include?(id)
144
- product_option_types.create({option_type_id: id}, without_protection: true) unless product_option_types.pluck(:option_type_id).include?(id)
114
+ product_option_types.create(option_type_id: id) unless product_option_types.pluck(:option_type_id).include?(id)
145
115
  end
146
116
  end
147
117
 
@@ -159,11 +129,8 @@ module Spree
159
129
  !!deleted_at
160
130
  end
161
131
 
162
- # determine if product is available.
163
- # deleted products and products with nil or future available_on date
164
- # are not available
165
132
  def available?
166
- !(available_on.nil? || available_on.future?) && !deleted?
133
+ !(available_on.nil? || available_on.future?)
167
134
  end
168
135
 
169
136
  # split variants list into hash which shows mapping of opt value onto matching variants
@@ -199,19 +166,14 @@ module Spree
199
166
  end
200
167
 
201
168
  def property(property_name)
202
- return nil unless prop = properties.find_by_name(property_name)
203
- product_properties.find_by_property_id(prop.id).try(:value)
169
+ return nil unless prop = properties.find_by(name: property_name)
170
+ product_properties.find_by(property: prop).try(:value)
204
171
  end
205
172
 
206
173
  def set_property(property_name, property_value)
207
174
  ActiveRecord::Base.transaction do
208
- # Works around spree_i18n #301
209
- property = if Property.exists?(name: property_name)
210
- Property.where(name: property_name).first
211
- else
212
- Property.create(name: property_name, presentation: property_name)
213
- end
214
- product_property = ProductProperty.where(product_id: self.id, property_id: property.id).first_or_initialize
175
+ property = Property.where(name: property_name).first_or_create!(presentation: property_name)
176
+ product_property = ProductProperty.where(product: self, property: property).first_or_initialize
215
177
  product_property.value = property_value
216
178
  product_property.save!
217
179
  end
@@ -223,10 +185,10 @@ module Spree
223
185
  end
224
186
 
225
187
  def total_on_hand
226
- if self.variants_including_master.any? { |v| !v.should_track_inventory? }
227
- Float::INFINITY
228
- else
188
+ if Spree::Config.track_inventory_levels
229
189
  self.stock_items.sum(&:count_on_hand)
190
+ else
191
+ Float::INFINITY
230
192
  end
231
193
  end
232
194
 
@@ -234,7 +196,7 @@ module Spree
234
196
  # which would make AR's default finder return nil.
235
197
  # This is a stopgap for that little problem.
236
198
  def master
237
- super || variants_including_master.with_deleted.where(:is_master => true).first
199
+ super || variants_including_master.with_deleted.where(is_master: true).first
238
200
  end
239
201
 
240
202
  private
@@ -246,15 +208,18 @@ module Spree
246
208
  values = values.inject(values.shift) { |memo, value| memo.product(value).map(&:flatten) }
247
209
 
248
210
  values.each do |ids|
249
- variant = variants.create({ option_value_ids: ids, price: master.price }, without_protection: true)
211
+ variant = variants.create(
212
+ option_value_ids: ids,
213
+ price: master.price
214
+ )
250
215
  end
251
216
  save
252
217
  end
253
218
 
254
219
  def add_properties_and_option_types_from_prototype
255
- if prototype_id && prototype = Spree::Prototype.find_by_id(prototype_id)
220
+ if prototype_id && prototype = Spree::Prototype.find_by(id: prototype_id)
256
221
  prototype.properties.each do |property|
257
- product_properties.create({property: property}, without_protection: true)
222
+ product_properties.create(property: property)
258
223
  end
259
224
  self.option_types = prototype.option_types
260
225
  end
@@ -268,7 +233,7 @@ module Spree
268
233
  # there's a weird quirk with the delegate stuff that does not automatically save the delegate object
269
234
  # when saving so we force a save using a hook.
270
235
  def save_master
271
- master.save if master && (master.changed? || master.new_record? || (master.default_price && (master.default_price.changed? || master.default_price.new_record?)))
236
+ master.save if master && (master.changed? || master.new_record? || (master.default_price && (master.default_price.changed || master.default_price.new_record)))
272
237
  end
273
238
 
274
239
  def ensure_master
@@ -278,7 +243,7 @@ module Spree
278
243
 
279
244
  def punch_permalink
280
245
  update_attribute :permalink, "#{Time.now.to_i}_#{permalink}" # punch permalink with date prefix
281
- end
246
+ end
282
247
  end
283
248
  end
284
249