spree_core 2.1.3 → 2.1.4

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/spree/admin/images_helper.rb +1 -1
  3. data/app/helpers/spree/base_helper.rb +5 -2
  4. data/app/models/spree/address.rb +9 -1
  5. data/app/models/spree/adjustment.rb +2 -2
  6. data/app/models/spree/calculator/default_tax.rb +5 -1
  7. data/app/models/spree/credit_card.rb +2 -0
  8. data/app/models/spree/gateway.rb +1 -1
  9. data/app/models/spree/inventory_unit.rb +5 -4
  10. data/app/models/spree/legacy_user.rb +2 -11
  11. data/app/models/spree/line_item.rb +2 -3
  12. data/app/models/spree/log_entry.rb +4 -0
  13. data/app/models/spree/option_type.rb +6 -0
  14. data/app/models/spree/option_value.rb +12 -1
  15. data/app/models/spree/order.rb +34 -16
  16. data/app/models/spree/order/checkout.rb +4 -0
  17. data/app/models/spree/order_inventory.rb +1 -1
  18. data/app/models/spree/payment.rb +10 -2
  19. data/app/models/spree/payment/processing.rb +5 -4
  20. data/app/models/spree/payment_method.rb +2 -0
  21. data/app/models/spree/price.rb +5 -0
  22. data/app/models/spree/product.rb +6 -5
  23. data/app/models/spree/product/scopes.rb +12 -6
  24. data/app/models/spree/product_property.rb +1 -1
  25. data/app/models/spree/promotion.rb +1 -8
  26. data/app/models/spree/promotion/rules/user_logged_in.rb +1 -3
  27. data/app/models/spree/property.rb +8 -0
  28. data/app/models/spree/shipment.rb +9 -14
  29. data/app/models/spree/shipping_method.rb +3 -2
  30. data/app/models/spree/shipping_rate.rb +7 -9
  31. data/app/models/spree/stock/estimator.rb +21 -14
  32. data/app/models/spree/stock/package.rb +1 -1
  33. data/app/models/spree/stock/packer.rb +1 -1
  34. data/app/models/spree/stock/quantifier.rb +11 -2
  35. data/app/models/spree/stock_item.rb +2 -2
  36. data/app/models/spree/stock_location.rb +8 -0
  37. data/app/models/spree/stock_movement.rb +3 -1
  38. data/app/models/spree/taxon.rb +2 -2
  39. data/app/models/spree/variant.rb +19 -4
  40. data/app/models/spree/zone.rb +1 -1
  41. data/app/views/spree/shared/_routes.html.erb +1 -1
  42. data/config/locales/en.yml +15 -1
  43. data/db/default/spree/countries.rb +7 -7
  44. data/db/migrate/20130417120034_add_index_to_source_columns_on_adjustments.rb +5 -0
  45. data/db/migrate/20130802022321_migrate_tax_categories_to_line_items.rb +5 -2
  46. data/db/migrate/20131026154747_add_track_inventory_to_variant.rb +5 -0
  47. data/db/migrate/20131120234456_add_updated_at_to_variants.rb +5 -0
  48. data/db/migrate/20131211192741_unique_shipping_method_categories.rb +24 -0
  49. data/db/migrate/20140120160805_add_index_to_variant_id_and_currency_on_prices.rb +5 -0
  50. data/lib/generators/spree/dummy/dummy_generator.rb +14 -3
  51. data/lib/generators/spree/dummy/templates/rails/database.yml +10 -0
  52. data/lib/spree/core.rb +3 -0
  53. data/lib/spree/core/controller_helpers/order.rb +4 -1
  54. data/lib/spree/core/controller_helpers/ssl.rb +5 -7
  55. data/lib/spree/core/controller_helpers/strong_parameters.rb +6 -0
  56. data/lib/spree/core/delegate_belongs_to.rb +16 -10
  57. data/lib/spree/core/engine.rb +11 -2
  58. data/lib/spree/core/mail_method.rb +27 -0
  59. data/lib/spree/core/mail_settings.rb +33 -38
  60. data/lib/spree/core/permalinks.rb +5 -1
  61. data/lib/spree/core/s3_support.rb +1 -1
  62. data/lib/spree/core/user_address.rb +30 -0
  63. data/lib/spree/core/validators/email.rb +9 -3
  64. data/lib/spree/core/version.rb +1 -1
  65. data/lib/spree/i18n.rb +1 -0
  66. data/lib/spree/migrations.rb +55 -0
  67. data/lib/spree/money.rb +171 -1
  68. data/lib/spree/permitted_attributes.rb +6 -6
  69. data/lib/spree/testing_support/capybara_ext.rb +6 -5
  70. data/lib/spree/testing_support/controller_requests.rb +20 -4
  71. data/lib/spree/testing_support/factories/product_factory.rb +4 -0
  72. data/lib/spree/testing_support/factories/variant_factory.rb +15 -0
  73. metadata +158 -164
@@ -0,0 +1,27 @@
1
+ module Spree
2
+ module Core
3
+ class MailMethod
4
+ def initialize(options={})
5
+ end
6
+
7
+ def deliver!(mail)
8
+ if Config.enable_mail_delivery
9
+ mailer.deliver!(mail)
10
+ end
11
+ end
12
+
13
+ def mailer
14
+ mailer_class.new(mail_server_settings)
15
+ end
16
+
17
+ private
18
+ def mailer_class
19
+ Rails.env.test?? Mail::TestMailer : Mail::SMTP
20
+ end
21
+
22
+ def mail_server_settings
23
+ MailSettings.new.mail_server_settings
24
+ end
25
+ end
26
+ end
27
+ end
@@ -8,53 +8,48 @@ module Spree
8
8
  # This makes it possible to configure the mail settings through an admin
9
9
  # interface instead of requiring changes to the Rails envrionment file
10
10
  def self.init
11
- self.new.override! if override?
11
+ override! if override?
12
12
  end
13
13
 
14
14
  def self.override?
15
15
  Config.override_actionmailer_config
16
16
  end
17
17
 
18
- def override!
19
- if Config.enable_mail_delivery
20
- ActionMailer::Base.default_url_options[:host] ||= Config.site_url
21
- ActionMailer::Base.smtp_settings = mail_server_settings
22
- ActionMailer::Base.perform_deliveries = true
23
- else
24
- ActionMailer::Base.perform_deliveries = false
25
- end
18
+ def self.override!
19
+ ActionMailer::Base.delivery_method = :spree
20
+ ActionMailer::Base.default_url_options[:host] ||= Config.site_url
21
+ end
22
+
23
+ def mail_server_settings
24
+ settings = if need_authentication?
25
+ basic_settings.merge(user_credentials)
26
+ else
27
+ basic_settings
28
+ end
29
+
30
+ settings.merge :enable_starttls_auto => secure_connection?
26
31
  end
27
32
 
28
33
  private
29
- def mail_server_settings
30
- settings = if need_authentication?
31
- basic_settings.merge(user_credentials)
32
- else
33
- basic_settings
34
- end
35
-
36
- settings.merge :enable_starttls_auto => secure_connection?
37
- end
38
-
39
- def user_credentials
40
- { :user_name => Config.smtp_username,
41
- :password => Config.smtp_password }
42
- end
43
-
44
- def basic_settings
45
- { :address => Config.mail_host,
46
- :domain => Config.mail_domain,
47
- :port => Config.mail_port,
48
- :authentication => Config.mail_auth_type }
49
- end
50
-
51
- def need_authentication?
52
- Config.mail_auth_type != 'None'
53
- end
54
-
55
- def secure_connection?
56
- Config.secure_connection_type == 'TLS'
57
- end
34
+ def user_credentials
35
+ { :user_name => Config.smtp_username,
36
+ :password => Config.smtp_password }
37
+ end
38
+
39
+ def basic_settings
40
+ { :address => Config.mail_host,
41
+ :domain => Config.mail_domain,
42
+ :port => Config.mail_port,
43
+ :authentication => Config.mail_auth_type }
44
+ end
45
+
46
+ def need_authentication?
47
+ Config.mail_auth_type != 'None'
48
+ end
49
+
50
+ def secure_connection?
51
+ Config.secure_connection_type == 'TLS'
52
+ end
58
53
  end
59
54
  end
60
55
  end
@@ -33,6 +33,10 @@ module Spree
33
33
  permalink_options[:prefix] || ""
34
34
  end
35
35
 
36
+ def permalink_length
37
+ permalink_options[:length] || 9
38
+ end
39
+
36
40
  def permalink_order
37
41
  order = permalink_options[:order]
38
42
  "#{order} ASC," if order
@@ -40,7 +44,7 @@ module Spree
40
44
  end
41
45
 
42
46
  def generate_permalink
43
- "#{self.class.permalink_prefix}#{Array.new(9){rand(9)}.join}"
47
+ "#{self.class.permalink_prefix}#{Array.new(self.class.permalink_length){rand(9)}.join}"
44
48
  end
45
49
 
46
50
  def save_permalink(permalink_value=self.to_param)
@@ -15,7 +15,7 @@ module Spree
15
15
  self.attachment_definitions[field][:s3_credentials] = s3_creds
16
16
  self.attachment_definitions[field][:s3_headers] = ActiveSupport::JSON.decode(config[:s3_headers])
17
17
  self.attachment_definitions[field][:bucket] = config[:s3_bucket]
18
- self.attachment_definitions[field][:s3_protocol] = config[:s3_protocol].downcase unless config[:s3_protocol].blank?
18
+ self.attachment_definitions[field][:s3_protocol] = config[:s3_protocol].blank? ? '' : config[:s3_protocol].downcase
19
19
  self.attachment_definitions[field][:s3_host_alias] = config[:s3_host_alias] unless config[:s3_host_alias].blank?
20
20
  end
21
21
  end
@@ -0,0 +1,30 @@
1
+ module Spree
2
+ module Core
3
+ module UserAddress
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ belongs_to :bill_address, foreign_key: :bill_address_id, class_name: 'Spree::Address'
8
+ alias_attribute :billing_address, :bill_address
9
+
10
+ belongs_to :ship_address, foreign_key: :ship_address_id, class_name: 'Spree::Address'
11
+ alias_attribute :shipping_address, :ship_address
12
+
13
+ def persist_order_address(order)
14
+ b_address = self.bill_address || self.build_bill_address
15
+ b_address.attributes = order.bill_address.attributes.except('id', 'updated_at', 'created_at')
16
+ b_address.save
17
+ self.update_attributes(bill_address_id: b_address.id)
18
+
19
+ # May not be present if delivery step has been removed
20
+ if order.ship_address
21
+ s_address = self.ship_address || self.build_ship_address
22
+ s_address.attributes = order.ship_address.attributes.except('id', 'updated_at', 'created_at')
23
+ s_address.save
24
+ self.update_attributes(ship_address_id: s_address.id)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -3,10 +3,16 @@
3
3
  require 'mail'
4
4
  class EmailValidator < ActiveModel::EachValidator
5
5
  def validate_each(record,attribute,value)
6
+ unless valid?(value)
7
+ record.errors.add(attribute, :invalid, {:value => value}.merge!(options))
8
+ end
9
+ end
10
+
11
+ def valid?(email)
6
12
  begin
7
- m = Mail::Address.new(value)
13
+ m = Mail::Address.new(email)
8
14
  # We must check that value contains a domain and that value is an email address
9
- r = m.domain && m.address == value
15
+ r = m.domain && m.address == email
10
16
  t = m.__send__(:tree)
11
17
  # We need to dig into treetop
12
18
  # A valid domain must have dot_atom_text elements size > 1
@@ -18,6 +24,6 @@ class EmailValidator < ActiveModel::EachValidator
18
24
  rescue Exception => e
19
25
  r = false
20
26
  end
21
- record.errors.add(attribute, :invalid, {:value => value}.merge!(options)) unless r
27
+ r
22
28
  end
23
29
  end
@@ -1,5 +1,5 @@
1
1
  module Spree
2
2
  def self.version
3
- "2.1.3"
3
+ "2.1.4"
4
4
  end
5
5
  end
data/lib/spree/i18n.rb CHANGED
@@ -4,6 +4,7 @@ require 'spree/i18n/base'
4
4
 
5
5
  module Spree
6
6
  extend ActionView::Helpers::TranslationHelper
7
+ extend ActionView::Helpers::TagHelper
7
8
 
8
9
  class << self
9
10
  # Add spree namespace and delegate to Rails TranslationHelper for some nice
@@ -0,0 +1,55 @@
1
+ module Spree
2
+ class Migrations
3
+ attr_reader :config, :engine_name
4
+
5
+ # Takes the engine config block and engine name
6
+ def initialize(config, engine_name)
7
+ @config, @engine_name = config, engine_name
8
+ end
9
+
10
+ # Puts warning when any engine migration is not present on the Rails app
11
+ # db/migrate dir
12
+ #
13
+ # First split:
14
+ #
15
+ # ["20131128203548", "update_name_fields_on_spree_credit_cards.spree.rb"]
16
+ #
17
+ # Second split should give the engine_name of the migration
18
+ #
19
+ # ["update_name_fields_on_spree_credit_cards", "spree.rb"]
20
+ #
21
+ # Shouldn't run on test mode because migrations inside engine don't have
22
+ # engine name on the file name
23
+ def check
24
+ if File.exists?("config/spree.yml") && File.directory?("db/migrate")
25
+ engine_in_app = app_migrations.map do |file_name|
26
+ name, engine = file_name.split(".", 2)
27
+ next unless engine == "#{engine_name}.rb"
28
+ name
29
+ end.compact! || []
30
+
31
+ unless (engine_migrations.sort - engine_in_app.sort).empty?
32
+ puts "[#{engine_name.capitalize} WARNING] Missing migrations." \
33
+ "Run `bundle exec rake railties:install:migrations` to get them.\n\n"
34
+ true
35
+ end
36
+ end
37
+ end
38
+
39
+ private
40
+ def engine_migrations
41
+ Dir.entries("#{config.root}/db/migrate").map do |file_name|
42
+ name = file_name.split("_", 2).last.split(".", 2).first
43
+ name.empty? ? next : name
44
+ end.compact! || []
45
+ end
46
+
47
+ def app_migrations
48
+ Dir.entries("db/migrate").map do |file_name|
49
+ next if [".", ".."].include? file_name
50
+ name = file_name.split("_", 2).last
51
+ name.empty? ? next : name
52
+ end.compact! || []
53
+ end
54
+ end
55
+ end
data/lib/spree/money.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'money'
2
4
 
3
5
  module Spree
@@ -7,7 +9,7 @@ module Spree
7
9
  delegate :cents, :to => :money
8
10
 
9
11
  def initialize(amount, options={})
10
- @money = ::Money.parse([amount, (options[:currency] || Spree::Config[:currency])].join)
12
+ @money = self.class.parse([amount, (options[:currency] || Spree::Config[:currency])].join)
11
13
  @options = {}
12
14
  @options[:with_currency] = Spree::Config[:display_currency]
13
15
  @options[:symbol_position] = Spree::Config[:currency_symbol_position].to_sym
@@ -20,6 +22,174 @@ module Spree
20
22
  @options[:symbol_position] = @options[:symbol_position].to_sym
21
23
  end
22
24
 
25
+ # This method is being deprecated in Money 6.1.0, so now lives here.
26
+ def self.parse(input, currency = nil)
27
+ i = input.to_s.strip
28
+
29
+ # raise Money::Currency.table.collect{|c| c[1][:symbol]}.inspect
30
+
31
+ # Check the first character for a currency symbol, alternatively get it
32
+ # from the stated currency string
33
+ c = if ::Money.assume_from_symbol && i =~ /^(\$|€|£)/
34
+ case i
35
+ when /^\$/ then "USD"
36
+ when /^€/ then "EUR"
37
+ when /^£/ then "GBP"
38
+ end
39
+ else
40
+ i[/[A-Z]{2,3}/]
41
+ end
42
+
43
+ # check that currency passed and embedded currency are the same,
44
+ # and negotiate the final currency
45
+ if currency.nil? and c.nil?
46
+ currency = ::Money.default_currency
47
+ elsif currency.nil?
48
+ currency = c
49
+ elsif c.nil?
50
+ currency = currency
51
+ elsif currency != c
52
+ # TODO: ParseError
53
+ raise ArgumentError, "Mismatching Currencies"
54
+ end
55
+ currency = ::Money::Currency.wrap(currency)
56
+
57
+ fractional = extract_cents(i, currency)
58
+ ::Money.new(fractional, currency)
59
+ end
60
+
61
+ # This method is being deprecated in Money 6.1.0, so now lives here.
62
+ def self.extract_cents(input, currency = Money.default_currency)
63
+ # remove anything that's not a number, potential thousands_separator, or minus sign
64
+ num = input.gsub(/[^\d.,'-]/, '')
65
+
66
+ # set a boolean flag for if the number is negative or not
67
+ negative = num =~ /^-|-$/ ? true : false
68
+
69
+ # decimal mark character
70
+ decimal_char = currency.decimal_mark
71
+
72
+ # if negative, remove the minus sign from the number
73
+ # if it's not negative, the hyphen makes the value invalid
74
+ if negative
75
+ num = num.sub(/^-|-$/, '')
76
+ end
77
+
78
+ raise ArgumentError, "Invalid currency amount (hyphen)" if num.include?('-')
79
+
80
+ #if the number ends with punctuation, just throw it out. If it means decimal,
81
+ #it won't hurt anything. If it means a literal period or comma, this will
82
+ #save it from being mis-interpreted as a decimal.
83
+ num.chop! if num.match(/[\.|,]$/)
84
+
85
+ # gather all decimal_marks within the result number
86
+ used_delimiters = num.scan(/[^\d]/)
87
+
88
+ # determine the number of unique decimal_marks within the number
89
+ #
90
+ # e.g.
91
+ # $1,234,567.89 would return 2 (, and .)
92
+ # $125,00 would return 1
93
+ # $199 would return 0
94
+ # $1 234,567.89 would raise an error (decimal_marks are space, comma, and period)
95
+ case used_delimiters.uniq.length
96
+ # no decimal_mark or thousands_separator; major (dollars) is the number, and minor (cents) is 0
97
+ when 0 then major, minor = num, 0
98
+
99
+ # two decimal_marks, so we know the last item in this array is the
100
+ # major/minor thousands_separator and the rest are decimal_marks
101
+ when 2
102
+ thousands_separator, decimal_mark = used_delimiters.uniq
103
+
104
+ # remove all thousands_separator, split on the decimal_mark
105
+ major, minor = num.gsub(thousands_separator, '').split(decimal_mark)
106
+ min = 0 unless min
107
+ when 1
108
+ # we can't determine if the comma or period is supposed to be a decimal_mark or a thousands_separator
109
+ # e.g.
110
+ # 1,00 - comma is a thousands_separator
111
+ # 1.000 - period is a thousands_separator
112
+ # 1,000 - comma is a decimal_mark
113
+ # 1,000,000 - comma is a decimal_mark
114
+ # 10000,00 - comma is a thousands_separator
115
+ # 1000,000 - comma is a thousands_separator
116
+
117
+ # assign first decimal_mark for reusability
118
+ decimal_mark = used_delimiters.first
119
+
120
+ # When we have identified the decimal mark character
121
+ if decimal_char == decimal_mark
122
+ major, minor = num.split(decimal_char)
123
+
124
+ else
125
+ # decimal_mark is used as a decimal_mark when there are multiple instances, always
126
+ if num.scan(decimal_mark).length > 1 # multiple matches; treat as decimal_mark
127
+ major, minor = num.gsub(decimal_mark, ''), 0
128
+ else
129
+ # ex: 1,000 - 1.0000 - 10001.000
130
+ # split number into possible major (dollars) and minor (cents) values
131
+ possible_major, possible_minor = num.split(decimal_mark)
132
+ possible_major ||= "0"
133
+ possible_minor ||= "00"
134
+
135
+ # if the minor (cents) length isn't 3, assign major/minor from the possibles
136
+ # e.g.
137
+ # 1,00 => 1.00
138
+ # 1.0000 => 1.00
139
+ # 1.2 => 1.20
140
+ if possible_minor.length != 3 # thousands_separator
141
+ major, minor = possible_major, possible_minor
142
+ else
143
+ # minor length is three
144
+ # let's try to figure out intent of the thousands_separator
145
+
146
+ # the major length is greater than three, which means
147
+ # the comma or period is used as a thousands_separator
148
+ # e.g.
149
+ # 1000,000
150
+ # 100000,000
151
+ if possible_major.length > 3
152
+ major, minor = possible_major, possible_minor
153
+ else
154
+ # number is in format ###{sep}### or ##{sep}### or #{sep}###
155
+ # handle as , is sep, . is thousands_separator
156
+ if decimal_mark == '.'
157
+ major, minor = possible_major, possible_minor
158
+ else
159
+ major, minor = "#{possible_major}#{possible_minor}", 0
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+ else
166
+ # TODO: ParseError
167
+ raise ArgumentError, "Invalid currency amount"
168
+ end
169
+
170
+ # build the string based on major/minor since decimal_mark/thousands_separator have been removed
171
+ # avoiding floating point arithmetic here to ensure accuracy
172
+ cents = (major.to_i * currency.subunit_to_unit)
173
+ # Because of an bug in JRuby, we can't just call #floor
174
+ minor = minor.to_s
175
+ minor = if minor.size < currency.decimal_places
176
+ (minor + ("0" * currency.decimal_places))[0,currency.decimal_places].to_i
177
+ elsif minor.size > currency.decimal_places
178
+ if minor[currency.decimal_places,1].to_i >= 5
179
+ minor[0,currency.decimal_places].to_i+1
180
+ else
181
+ minor[0,currency.decimal_places].to_i
182
+ end
183
+ else
184
+ minor.to_i
185
+ end
186
+
187
+ cents += minor
188
+
189
+ # if negative, multiply by -1; otherwise, return positive cents
190
+ negative ? cents * -1 : cents
191
+ end
192
+
23
193
  def to_s
24
194
  @money.format(@options)
25
195
  end