spree_core 2.1.3 → 2.1.4

Sign up to get free protection for your applications and to get access to all the features.
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