shopify-money 2.2.2 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +8 -0
  3. data/.github/workflows/tests.yml +3 -3
  4. data/.gitignore +2 -1
  5. data/.rubocop.yml +36 -0
  6. data/.ruby-version +1 -1
  7. data/Gemfile +4 -1
  8. data/Gemfile.lock +167 -121
  9. data/README.md +10 -4
  10. data/Rakefile +2 -2
  11. data/config/currency_historic.yml +44 -2
  12. data/config/currency_iso.yml +38 -64
  13. data/lib/money/allocator.rb +13 -9
  14. data/lib/money/config.rb +8 -0
  15. data/lib/money/core_extensions.rb +11 -8
  16. data/lib/money/currency/loader.rb +4 -3
  17. data/lib/money/currency.rb +12 -3
  18. data/lib/money/deprecations.rb +22 -4
  19. data/lib/money/errors.rb +1 -0
  20. data/lib/money/helpers.rb +3 -6
  21. data/lib/money/money.rb +58 -35
  22. data/lib/money/null_currency.rb +11 -3
  23. data/lib/money/parser/accounting.rb +1 -0
  24. data/lib/money/parser/fuzzy.rb +16 -23
  25. data/lib/money/parser/locale_aware.rb +3 -6
  26. data/lib/money/parser/simple.rb +2 -1
  27. data/lib/money/rails/job_argument_serializer.rb +0 -1
  28. data/lib/money/railtie.rb +5 -3
  29. data/lib/money/splitter.rb +20 -24
  30. data/lib/money/version.rb +2 -1
  31. data/lib/money.rb +1 -0
  32. data/lib/money_column/active_record_hooks.rb +9 -6
  33. data/lib/money_column/active_record_type.rb +7 -4
  34. data/lib/money_column/railtie.rb +2 -1
  35. data/lib/money_column.rb +1 -0
  36. data/lib/rubocop/cop/money/missing_currency.rb +16 -23
  37. data/lib/rubocop/cop/money/zero_money.rb +7 -13
  38. data/lib/shopify-money.rb +1 -0
  39. data/money.gemspec +6 -6
  40. data/spec/config_spec.rb +14 -0
  41. data/spec/core_extensions_spec.rb +6 -2
  42. data/spec/deprecations_spec.rb +1 -2
  43. data/spec/helpers_spec.rb +2 -5
  44. data/spec/money_spec.rb +49 -14
  45. data/spec/parser/accounting_spec.rb +2 -5
  46. data/spec/parser/fuzzy_spec.rb +7 -16
  47. data/spec/rubocop/cop/money/missing_currency_spec.rb +7 -7
  48. data/spec/rubocop/cop/money/zero_money_spec.rb +2 -2
  49. metadata +19 -42
data/lib/money/money.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'forwardable'
4
+ require 'json'
3
5
 
4
6
  class Money
5
7
  include Comparable
@@ -8,6 +10,7 @@ class Money
8
10
  NULL_CURRENCY = NullCurrency.new.freeze
9
11
 
10
12
  attr_reader :value, :currency
13
+
11
14
  def_delegators :@value, :zero?, :nonzero?, :positive?, :negative?, :to_i, :to_f, :hash
12
15
 
13
16
  class ReverseOperationProxy
@@ -36,11 +39,17 @@ class Money
36
39
 
37
40
  class << self
38
41
  extend Forwardable
39
- attr_accessor :config
40
- def_delegators :@config, :default_currency, :default_currency=
42
+ def_delegators :config, :default_currency, :default_currency=, :without_legacy_deprecations
43
+
44
+ def config
45
+ Thread.current[:shopify_money__config] ||= Config.new
46
+ end
47
+
48
+ def config=(config)
49
+ Thread.current[:shopify_money__config] = config
50
+ end
41
51
 
42
52
  def configure
43
- self.config ||= Config.new
44
53
  yield(config) if block_given?
45
54
  end
46
55
 
@@ -74,6 +83,16 @@ class Money
74
83
  new(value, currency)
75
84
  end
76
85
 
86
+ def from_json(string)
87
+ hash = JSON.parse(string, symbolize_names: true)
88
+ Money.new(hash.fetch(:value), hash.fetch(:currency))
89
+ end
90
+
91
+ def from_hash(hash)
92
+ hash = hash.transform_keys(&:to_sym)
93
+ Money.new(hash.fetch(:value), hash.fetch(:currency))
94
+ end
95
+
77
96
  def rational(money1, money2)
78
97
  money1.send(:arithmetic, money2) do
79
98
  factor = money1.currency.subunit_to_unit * money2.currency.subunit_to_unit
@@ -94,13 +113,11 @@ class Money
94
113
  # I18n.with_locale and ActiveSupport's Time.use_zone. This won't affect
95
114
  # instances being created with explicitly set currency.
96
115
  def with_currency(new_currency)
97
- begin
98
- old_currency = Money.current_currency
99
- Money.current_currency = new_currency
100
- yield
101
- ensure
102
- Money.current_currency = old_currency
103
- end
116
+ old_currency = Money.current_currency
117
+ Money.current_currency = new_currency
118
+ yield
119
+ ensure
120
+ Money.current_currency = old_currency
104
121
  end
105
122
 
106
123
  private
@@ -116,10 +133,12 @@ class Money
116
133
  return amount
117
134
  end
118
135
 
119
- msg = "Money.new(Money.new(amount, #{amount.currency}), #{currency}) is changing the currency of an existing money object"
136
+ msg = "Money.new(Money.new(amount, #{amount.currency}), #{currency}) " \
137
+ "is changing the currency of an existing money object"
138
+
120
139
  if Money.config.legacy_deprecations
121
140
  Money.deprecate("#{msg}. A Money::IncompatibleCurrencyError will raise in the next major release")
122
- return Money.new(amount.value, currency)
141
+ Money.new(amount.value, currency)
123
142
  else
124
143
  raise Money::IncompatibleCurrencyError, msg
125
144
  end
@@ -184,19 +203,19 @@ class Money
184
203
  end
185
204
  end
186
205
 
187
- def *(numeric)
188
- raise ArgumentError, "Money objects can only be multiplied by a Numeric" unless numeric.is_a?(Numeric)
206
+ def *(other)
207
+ raise ArgumentError, "Money objects can only be multiplied by a Numeric" unless other.is_a?(Numeric)
189
208
 
190
- return self if numeric == 1
191
- Money.new(value.to_r * numeric, currency)
209
+ return self if other == 1
210
+ Money.new(value.to_r * other, currency)
192
211
  end
193
212
 
194
- def /(numeric)
213
+ def /(other)
195
214
  raise "[Money] Dividing money objects can lose pennies. Use #split instead"
196
215
  end
197
216
 
198
217
  def inspect
199
- "#<#{self.class} value:#{self} currency:#{self.currency}>"
218
+ "#<#{self.class} value:#{self} currency:#{currency}>"
200
219
  end
201
220
 
202
221
  def ==(other)
@@ -215,6 +234,10 @@ class Money
215
234
  [ReverseOperationProxy.new(other), self]
216
235
  end
217
236
 
237
+ def convert_currency(exchange_rate, new_currency)
238
+ Money.new(value * exchange_rate, new_currency)
239
+ end
240
+
218
241
  def to_money(new_currency = nil)
219
242
  if new_currency.nil?
220
243
  return self
@@ -224,8 +247,10 @@ class Money
224
247
  return Money.new(value, new_currency)
225
248
  end
226
249
 
227
- ensure_compatible_currency(Helpers.value_to_currency(new_currency),
228
- "to_money is attempting to change currency of an existing money object from #{currency} to #{new_currency}")
250
+ ensure_compatible_currency(
251
+ Helpers.value_to_currency(new_currency),
252
+ "to_money is attempting to change currency of an existing money object from #{currency} to #{new_currency}",
253
+ )
229
254
 
230
255
  self
231
256
  end
@@ -246,7 +271,7 @@ class Money
246
271
 
247
272
  rounded_value = value.round(units)
248
273
  if units == 0
249
- sprintf("%d", rounded_value)
274
+ format("%d", rounded_value)
250
275
  else
251
276
  formatted = rounded_value.to_s("F")
252
277
  decimal_digits = formatted.size - formatted.index(".") - 1
@@ -260,7 +285,7 @@ class Money
260
285
  alias_method :to_formatted_s, :to_fs
261
286
 
262
287
  def to_json(options = nil)
263
- if (options.is_a?(Hash) && options.delete(:legacy_format)) || Money.config.legacy_json_format
288
+ if (options.is_a?(Hash) && options[:legacy_format]) || Money.config.legacy_json_format
264
289
  to_s
265
290
  else
266
291
  as_json(options).to_json
@@ -268,12 +293,13 @@ class Money
268
293
  end
269
294
 
270
295
  def as_json(options = nil)
271
- if (options.is_a?(Hash) && options.delete(:legacy_format)) || Money.config.legacy_json_format
296
+ if (options.is_a?(Hash) && options[:legacy_format]) || Money.config.legacy_json_format
272
297
  to_s
273
298
  else
274
299
  { value: to_s(:amount), currency: currency.to_s }
275
300
  end
276
301
  end
302
+ alias_method :to_h, :as_json
277
303
 
278
304
  def abs
279
305
  abs = value.abs
@@ -287,7 +313,7 @@ class Money
287
313
  Money.new(floor, currency)
288
314
  end
289
315
 
290
- def round(ndigits=0)
316
+ def round(ndigits = 0)
291
317
  round = value.round(ndigits)
292
318
  return self if round == value
293
319
  Money.new(round, currency)
@@ -348,13 +374,13 @@ class Money
348
374
  def clamp(min, max)
349
375
  raise ArgumentError, 'min cannot be greater than max' if min > max
350
376
 
351
- clamped_value = min if self.value < min
352
- clamped_value = max if self.value > max
377
+ clamped_value = min if value < min
378
+ clamped_value = max if value > max
353
379
 
354
380
  if clamped_value.nil?
355
381
  self
356
382
  else
357
- Money.new(clamped_value, self.currency)
383
+ Money.new(clamped_value, currency)
358
384
  end
359
385
  end
360
386
 
@@ -363,20 +389,17 @@ class Money
363
389
  def arithmetic(other)
364
390
  case other
365
391
  when Money
366
- ensure_compatible_currency(other.currency,
367
- "mathematical operation not permitted for Money objects with different currencies #{other.currency} and #{currency}.")
392
+ desc = "mathematical operation not permitted for Money objects with different currencies " \
393
+ "#{other.currency} and #{currency}."
394
+
395
+ ensure_compatible_currency(other.currency, desc)
368
396
  yield(other)
369
397
 
370
398
  when Numeric, String
371
399
  yield(Money.new(other, currency))
372
400
 
373
401
  else
374
- if Money.config.legacy_deprecations && other.respond_to?(:to_money)
375
- Money.deprecate("#{other.inspect} is being implicitly coerced into a Money object. Call `to_money` on this object to transform it into a money explicitly. An TypeError will raise in the next major release")
376
- yield(other.to_money(currency))
377
- else
378
- raise TypeError, "#{other.class.name} can't be coerced into Money"
379
- end
402
+ raise TypeError, "#{other.class.name} can't be coerced into a Money object"
380
403
  end
381
404
  end
382
405
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Money
3
4
  # A placeholder currency for instances where no actual currency is available,
4
5
  # as defined by ISO4217. You should rarely, if ever, need to use this
@@ -32,9 +33,16 @@ class Money
32
33
  # #=> #<Money value:5.00 currency:CAD>
33
34
  #
34
35
  class NullCurrency
35
-
36
- attr_reader :iso_code, :iso_numeric, :name, :smallest_denomination, :subunit_symbol,
37
- :subunit_to_unit, :minor_units, :symbol, :disambiguate_symbol, :decimal_mark
36
+ attr_reader :iso_code,
37
+ :iso_numeric,
38
+ :name,
39
+ :smallest_denomination,
40
+ :subunit_symbol,
41
+ :subunit_to_unit,
42
+ :minor_units,
43
+ :symbol,
44
+ :disambiguate_symbol,
45
+ :decimal_mark
38
46
 
39
47
  def initialize
40
48
  @symbol = '$'
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Money
3
4
  module Parser
4
5
  class Accounting < Fuzzy
@@ -1,43 +1,44 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Money
3
4
  module Parser
4
5
  class Fuzzy
5
6
  class MoneyFormatError < ArgumentError; end
6
7
 
7
- MARKS = %w[. , · ’ ˙ '] + [' ']
8
+ MARKS = ['.', ',', '·', '', '˙', "'", ' '].freeze
8
9
 
9
10
  ESCAPED_MARKS = Regexp.escape(MARKS.join)
10
11
  ESCAPED_NON_SPACE_MARKS = Regexp.escape((MARKS - [' ']).join)
11
12
  ESCAPED_NON_DOT_MARKS = Regexp.escape((MARKS - ['.']).join)
12
13
  ESCAPED_NON_COMMA_MARKS = Regexp.escape((MARKS - [',']).join)
13
14
 
14
- NUMERIC_REGEX = /(
15
+ NUMERIC_REGEX = %r{(
15
16
  [\+\-]?
16
17
  [\d#{ESCAPED_NON_SPACE_MARKS}][\d#{ESCAPED_MARKS}]*
17
- )/ix
18
+ )}ix
18
19
 
19
20
  # 1,234,567.89
20
- DOT_DECIMAL_REGEX = /\A
21
+ DOT_DECIMAL_REGEX = %r{\A
21
22
  [\+\-]?
22
23
  (?:
23
24
  (?:\d+)
24
25
  (?:[#{ESCAPED_NON_DOT_MARKS}]\d{3})+
25
26
  (?:\.\d{2,})?
26
27
  )
27
- \z/ix
28
+ \z}ix
28
29
 
29
30
  # 1.234.567,89
30
- COMMA_DECIMAL_REGEX = /\A
31
+ COMMA_DECIMAL_REGEX = %r{\A
31
32
  [\+\-]?
32
33
  (?:
33
34
  (?:\d+)
34
35
  (?:[#{ESCAPED_NON_COMMA_MARKS}]\d{3})+
35
36
  (?:\,\d{2,})?
36
37
  )
37
- \z/ix
38
+ \z}ix
38
39
 
39
40
  # 12,34,567.89
40
- INDIAN_NUMERIC_REGEX = /\A
41
+ INDIAN_NUMERIC_REGEX = %r{\A
41
42
  [\+\-]?
42
43
  (?:
43
44
  (?:\d+)
@@ -45,17 +46,17 @@ class Money
45
46
  (?:\,\d{3})
46
47
  (?:\.\d{2})?
47
48
  )
48
- \z/ix
49
+ \z}ix
49
50
 
50
51
  # 1,1123,4567.89
51
- CHINESE_NUMERIC_REGEX = /\A
52
+ CHINESE_NUMERIC_REGEX = %r{\A
52
53
  [\+\-]?
53
54
  (?:
54
55
  (?:\d+)
55
56
  (?:\,\d{4})+
56
57
  (?:\.\d{2})?
57
58
  )
58
- \z/ix
59
+ \z}ix
59
60
 
60
61
  def self.parse(input, currency = nil, **options)
61
62
  new.parse(input, currency, **options)
@@ -92,8 +93,7 @@ class Money
92
93
  number = number.to_s.strip
93
94
 
94
95
  if number.empty?
95
- if Money.config.legacy_deprecations && !strict
96
- Money.deprecate("invalid money strings will raise in the next major release \"#{input}\"")
96
+ if !strict
97
97
  return '0'
98
98
  else
99
99
  raise MoneyFormatError, "invalid money string: #{input}"
@@ -112,17 +112,15 @@ class Money
112
112
  # remove end of string mark
113
113
  number.sub!(/[#{ESCAPED_MARKS}]\z/, '')
114
114
 
115
- if amount = number[DOT_DECIMAL_REGEX] || number[INDIAN_NUMERIC_REGEX] || number[CHINESE_NUMERIC_REGEX]
115
+ if (amount = number[DOT_DECIMAL_REGEX] || number[INDIAN_NUMERIC_REGEX] || number[CHINESE_NUMERIC_REGEX])
116
116
  return amount.tr(ESCAPED_NON_DOT_MARKS, '')
117
117
  end
118
118
 
119
- if amount = number[COMMA_DECIMAL_REGEX]
119
+ if (amount = number[COMMA_DECIMAL_REGEX])
120
120
  return amount.tr(ESCAPED_NON_COMMA_MARKS, '').sub(',', '.')
121
121
  end
122
122
 
123
- if Money.config.legacy_deprecations && !strict
124
- Money.deprecate("invalid money strings will raise in the next major release \"#{input}\"")
125
- else
123
+ if strict
126
124
  raise MoneyFormatError, "invalid money string: #{input}"
127
125
  end
128
126
 
@@ -161,11 +159,6 @@ class Money
161
159
  return true
162
160
  end
163
161
 
164
- # legacy support for 1.000 USD
165
- if digits.last.size == 3 && digits.first.size <= 3 && currency.minor_units < 3
166
- return false
167
- end
168
-
169
162
  # The last mark matches the one used by the provided currency to delimiter decimals
170
163
  currency.decimal_mark == last_mark
171
164
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Money
3
4
  module Parser
4
5
  class LocaleAware
@@ -7,18 +8,14 @@ class Money
7
8
  class << self
8
9
  # The +Proc+ called to get the current locale decimal separator. In Rails apps this defaults to the same lookup
9
10
  # ActionView's +number_to_currency+ helper will use to format the monetary amount for display.
10
- def decimal_separator_resolver
11
- @decimal_separator_resolver
12
- end
11
+ attr_reader :decimal_separator_resolver
13
12
 
14
13
  # Set the default +Proc+ to determine the current locale decimal separator.
15
14
  #
16
15
  # @example
17
16
  # Money::Parser::LocaleAware.decimal_separator_resolver =
18
17
  # ->() { MyFormattingLibrary.current_locale.decimal.separator }
19
- def decimal_separator_resolver=(proc)
20
- @decimal_separator_resolver = proc
21
- end
18
+ attr_writer :decimal_separator_resolver
22
19
 
23
20
  # Parses an input string, normalizing some non-ASCII characters to their equivalent ASCII, then discarding any
24
21
  # character that is not a digit, hyphen-minus or the decimal separator. To prevent user confusion, make sure
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Money
3
4
  module Parser
4
5
  class Simple
5
- SIGNED_DECIMAL_MATCHER = /\A-?\d*(?:\.\d*)?\z/.freeze
6
+ SIGNED_DECIMAL_MATCHER = /\A-?\d*(?:\.\d*)?\z/
6
7
 
7
8
  class << self
8
9
  # Parses an input string using BigDecimal, it always expects a dot character as a decimal separator and
@@ -19,4 +19,3 @@ class Money
19
19
  end
20
20
  end
21
21
  end
22
-
data/lib/money/railtie.rb CHANGED
@@ -3,9 +3,11 @@
3
3
  class Money
4
4
  class Railtie < Rails::Railtie
5
5
  initializer "shopify-money.setup_active_job_serializer" do
6
- ActiveSupport.on_load :active_job do
7
- require_relative "rails/job_argument_serializer"
8
- ActiveJob::Serializers.add_serializers ::Money::Rails::JobArgumentSerializer
6
+ ActiveSupport.on_load(:active_job) do
7
+ if defined?(ActiveJob::Serializers)
8
+ require_relative "rails/job_argument_serializer"
9
+ ActiveJob::Serializers.add_serializers(::Money::Rails::JobArgumentSerializer)
10
+ end
9
11
  end
10
12
  end
11
13
 
@@ -11,7 +11,7 @@ class Money
11
11
  @split = nil
12
12
  end
13
13
 
14
- protected attr_writer :split
14
+ protected attr_writer(:split)
15
15
 
16
16
  def split
17
17
  @split ||= begin
@@ -35,19 +35,17 @@ class Money
35
35
  each do |money|
36
36
  return money
37
37
  end
38
+ elsif count >= size
39
+ to_a
38
40
  else
39
- if count >= size
40
- to_a
41
- else
42
- result = Array.new(count)
43
- index = 0
44
- each do |money|
45
- result[index] = money
46
- index += 1
47
- break if index == count
48
- end
49
- result
41
+ result = Array.new(count)
42
+ index = 0
43
+ each do |money|
44
+ result[index] = money
45
+ index += 1
46
+ break if index == count
50
47
  end
48
+ result
51
49
  end
52
50
  end
53
51
 
@@ -56,20 +54,18 @@ class Money
56
54
  reverse_each do |money|
57
55
  return money
58
56
  end
57
+ elsif count >= size
58
+ to_a
59
59
  else
60
- if count >= size
61
- to_a
62
- else
63
- result = Array.new(count)
64
- index = 0
65
- reverse_each do |money|
66
- result[index] = money
67
- index += 1
68
- break if index == count
69
- end
70
- result.reverse!
71
- result
60
+ result = Array.new(count)
61
+ index = 0
62
+ reverse_each do |money|
63
+ result[index] = money
64
+ index += 1
65
+ break if index == count
72
66
  end
67
+ result.reverse!
68
+ result
73
69
  end
74
70
  end
75
71
 
data/lib/money/version.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class Money
3
- VERSION = "2.2.2"
4
+ VERSION = "3.0.1"
4
5
  end
data/lib/money.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'money/version'
3
4
  require_relative 'money/parser/fuzzy'
4
5
  require_relative 'money/helpers'
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module MoneyColumn
3
4
  class CurrencyReadOnlyError < StandardError; end
4
5
 
@@ -82,7 +83,7 @@ module MoneyColumn
82
83
  currency_column: currency_column,
83
84
  currency: currency,
84
85
  currency_read_only: currency_read_only,
85
- coerce_null: coerce_null
86
+ coerce_null: coerce_null,
86
87
  )
87
88
 
88
89
  if options[:currency_column]
@@ -96,11 +97,11 @@ module MoneyColumn
96
97
 
97
98
  attribute(column_string, MoneyColumn::ActiveRecordType.new)
98
99
 
99
- define_method column do
100
+ define_method(column) do
100
101
  read_money_attribute(column_string)
101
102
  end
102
103
 
103
- define_method "#{column}=" do |money|
104
+ define_method("#{column}=") do |money|
104
105
  write_money_attribute(column_string, money)
105
106
  end
106
107
  end
@@ -109,8 +110,10 @@ module MoneyColumn
109
110
  private
110
111
 
111
112
  def normalize_money_column_options(options)
112
- raise ArgumentError, 'cannot set both :currency_column and :currency options' if options[:currency] && options[:currency_column]
113
- raise ArgumentError, 'must set one of :currency_column or :currency options' unless options[:currency] || options[:currency_column]
113
+ raise ArgumentError,
114
+ 'cannot set both :currency_column and :currency options' if options[:currency] && options[:currency_column]
115
+ raise ArgumentError,
116
+ 'must set one of :currency_column or :currency options' unless options[:currency] || options[:currency_column]
114
117
 
115
118
  if options[:currency]
116
119
  options[:currency] = Money::Currency.find!(options[:currency]).to_s.freeze
@@ -126,7 +129,7 @@ module MoneyColumn
126
129
  def clear_cache_on_currency_change(currency_column)
127
130
  return if money_column_options.any? { |_, opt| opt[:currency_column] == currency_column }
128
131
 
129
- define_method "#{currency_column}=" do |value|
132
+ define_method("#{currency_column}=") do |value|
130
133
  clear_money_column_cache
131
134
  super(value)
132
135
  end
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
- class MoneyColumn::ActiveRecordType < ActiveRecord::Type::Decimal
3
- def serialize(money)
4
- return nil unless money
5
- super(money.to_d)
2
+
3
+ module MoneyColumn
4
+ class ActiveRecordType < ActiveRecord::Type::Decimal
5
+ def serialize(money)
6
+ return unless money
7
+ super(money.to_d)
8
+ end
6
9
  end
7
10
  end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module MoneyColumn
3
4
  class Railtie < Rails::Railtie
4
- ActiveSupport.on_load :active_record do
5
+ ActiveSupport.on_load(:active_record) do
5
6
  ActiveRecord::Base.send(:include, MoneyColumn::ActiveRecordHooks)
6
7
  end
7
8
  end
data/lib/money_column.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'money_column/active_record_hooks'
3
4
  require_relative 'money_column/active_record_type'
4
5
  require_relative 'money_column/railtie'
@@ -3,7 +3,8 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Money
6
- class MissingCurrency < Cop
6
+ class MissingCurrency < Base
7
+ extend RuboCop::Cop::AutoCorrector
7
8
  # `Money.new()` without a currency argument cannot guarantee correctness:
8
9
  # - no error raised for cross-currency computation (e.g. 5 CAD + 5 USD)
9
10
  # - #subunits returns wrong values for 0 and 3 decimals currencies
@@ -32,48 +33,40 @@ module RuboCop
32
33
  PATTERN
33
34
 
34
35
  def on_send(node)
36
+ receiver, method, _ = *node
37
+
35
38
  money_new(node) do |amount, currency_arg|
36
39
  return if amount&.splat_type?
37
40
  return if currency_arg
38
41
 
39
- add_offense(node, message: 'Money is missing currency argument')
40
- end
41
-
42
- if to_money_block?(node) || to_money_without_currency?(node)
43
- add_offense(node, message: 'to_money is missing currency argument')
44
- end
45
- end
46
- alias on_csend on_send
47
-
48
- def autocorrect(node)
49
- receiver, method, _ = *node
50
-
51
- lambda do |corrector|
52
- money_new(node) do |amount, currency_arg|
53
- return if currency_arg
54
-
42
+ add_offense(node, message: 'Money is missing currency argument') do |corrector|
55
43
  corrector.replace(
56
44
  node.loc.expression,
57
- "#{receiver.source}.#{method}(#{amount&.source || 0}, #{replacement_currency})"
45
+ "#{receiver.source}.#{method}(#{amount&.source || 0}, #{replacement_currency})",
58
46
  )
59
47
  end
48
+ end
60
49
 
61
- if to_money_without_currency?(node)
62
- corrector.insert_after(node.loc.expression, "(#{replacement_currency})")
63
- elsif to_money_block?(node)
50
+ if to_money_block?(node)
51
+ add_offense(node, message: 'to_money is missing currency argument') do |corrector|
64
52
  corrector.replace(
65
53
  node.loc.expression,
66
- "#{receiver.source}.#{method} { |x| x.to_money(#{replacement_currency}) }"
54
+ "#{receiver.source}.#{method} { |x| x.to_money(#{replacement_currency}) }",
67
55
  )
68
56
  end
57
+ elsif to_money_without_currency?(node)
58
+ add_offense(node, message: 'to_money is missing currency argument') do |corrector|
59
+ corrector.insert_after(node.loc.expression, "(#{replacement_currency})")
60
+ end
69
61
  end
70
62
  end
63
+ alias_method :on_csend, :on_send
71
64
 
72
65
  private
73
66
 
74
67
  def replacement_currency
75
68
  if cop_config['ReplacementCurrency']
76
- "'#{cop_config['ReplacementCurrency']}'"
69
+ "'#{cop_config["ReplacementCurrency"]}'"
77
70
  else
78
71
  'Money::NULL_CURRENCY'
79
72
  end