money-rails 1.13.0 → 1.14.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 17a8dc1cf4b600e6893a143ea21e6a9bef017371
4
- data.tar.gz: 21aa33f3cace05847f66f822b88b46059752ed99
2
+ SHA256:
3
+ metadata.gz: 8226863e954671b603c22fac3eae1667b81ba88eb3425b8c9cd798e2cfa2f303
4
+ data.tar.gz: cc9c7c8fbc27bcc9343ad457f596b15594a576d0da93448d3bfbabe1caedcfbb
5
5
  SHA512:
6
- metadata.gz: 3d8d1a12dccd30071f5e269b679b55a733fb2eaedf9ed694ed8dcd9c02ff95315293fe97387559e5d891240489f79c9acd57b79861a49c68cf6eeab387f5a032
7
- data.tar.gz: 87e4fd7469311f179282531901437773316083fb695dc12003f5767d9d61c9a40cb06d553f7cdc17e4246dbbbb5b0d10973a64417bd602ee8060ddb7c42d9fa3
6
+ metadata.gz: 0ed641980d3b776a3b2ec80d9ff1e7247415248eeff0bdc3fbc7077ad7700171fdcbda6c4c13bf39c2cda063a9fdf2575b660f9625fb506e8d39a815b96ab02f
7
+ data.tar.gz: 301fbd73c779ac54d60e1be7137df6e0a89974b0372d1667fe9eb69c2a8e40c309768bcda6d885f1d6e7168512d2e62a61562ea2189dcd1bba14c53d58b1b891
data/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.14.0
4
+
5
+ - Tweaks to support Ruby 3.0
6
+
7
+ ## 1.13.4
8
+
9
+ - Fix validator race condition
10
+ - Add Danish translation for errors
11
+ - Change Money fields to Decimal in Rails Admin
12
+ - Run hooks after active_record.initialize_database
13
+ - Add optional currency argument to "#currency_symbol" helper
14
+ - Rails 6.1 support
15
+
16
+ ## 1.13.3
17
+
18
+ - Add Money#to_hash for JSON serialization
19
+ - Update initializer template with #locale_backend config
20
+ - Rollback support for remove_monetize / remove_money DB helpers
21
+ - Rails 6 support
22
+
23
+ ## 1.13.2
24
+
25
+ - Make validation compatible with Money.locale_backend
26
+
27
+ ## 1.13.1
28
+
29
+ - Add a guard clause for "blank form" input (Mongoid)
30
+ - Do not add extra errors in case attribute is not a number
31
+ - Use Money.locale_backend instead of Money.use_i18n
32
+ - Add money_only_cents helper method
33
+
3
34
  ## 1.13.0
4
35
 
5
36
  - Bump money version to ~> 6.13.0
data/LICENSE CHANGED
@@ -1,22 +1,22 @@
1
- Copyright (c) 2012 Andreas Loupasakis
2
-
3
1
  MIT License
4
2
 
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
3
+ Copyright (c) 2012 Andreas Loupasakis
4
+ Copyright (c) 2021 Shane Emmons
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
12
 
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
15
 
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
data/README.md CHANGED
@@ -116,7 +116,7 @@ end
116
116
 
117
117
  Notice. Default value of currency field, generated by migration's helper, is USD. To override these defaults, you need change default_currency in money initializer and run migrations.
118
118
 
119
- The ```add_money``` helper is reversible, so you an use it inside ```change```
119
+ The ```add_money``` helper is reversible, so you can use it inside ```change```
120
120
  migrations. If you're writing separate ```up``` and ```down``` methods, you
121
121
  can use the ```remove_money``` helper.
122
122
 
@@ -306,7 +306,7 @@ object using EUR as their currency, instead of the default USD.
306
306
 
307
307
  By passing the option ```:with_currency``` to the ```monetize``` macro call,
308
308
  with a currency code (symbol or string) or a callable object (object that responds to the method ```call```) that returns a currency code, as its value, you can define a currency in a more granular
309
- way. This will you attach the given currency only to the specified monetized model
309
+ way. This will let you attach the given currency only to the specified monetized model
310
310
  attribute (allowing you to, for example, monetize different attributes of the same model with different currencies.).
311
311
 
312
312
  This allows you to override both the model level and the global
@@ -326,7 +326,7 @@ end
326
326
  ```
327
327
 
328
328
  In this case ```product.bonus``` will return a Money object with GBP as its
329
- currency, whereas ```product.discount.currency_as_string # => EUR ```
329
+ currency, whereas ```product.discount.currency.to_s # => EUR ```
330
330
 
331
331
  As mentioned earlier you can use an object that responds to the method ```call``` and accepts the model instance as a parameter. That means you can use a ```Proc``` or ```lambda``` (we would recommend ```lambda``` over ```Proc``` because of their [different control flow characteristics](https://stackoverflow.com/questions/1740046/whats-the-difference-between-a-proc-and-a-lambda-in-ruby)) or even define a separate ```class``` with an instance or class method (maybe even a ```module```) to return the currency code:
332
332
 
@@ -502,6 +502,7 @@ _For examples below, `@money_object == <Money fractional:650 currency:USD>`_
502
502
  | `humanized_money_with_symbol @money_object` | $6.50 |
503
503
  | `money_without_cents @money_object` | 6 |
504
504
  | `money_without_cents_and_with_symbol @money_object` | $6 |
505
+ | `money_only_cents @money_object` | 50 |
505
506
 
506
507
  #### `no_cents_if_whole`
507
508
 
@@ -552,7 +553,7 @@ For examples on using the test_helpers look at
552
553
  ## Supported ORMs/ODMs
553
554
 
554
555
  * ActiveRecord (>= 3.x)
555
- * Mongoid (2.x, 3.x)
556
+ * Mongoid (>= 2.x)
556
557
 
557
558
  ## Supported Ruby interpreters
558
559
 
@@ -586,4 +587,4 @@ If you are testing against mongoid, make sure to have the mongod process running
586
587
 
587
588
  ## License
588
589
 
589
- MIT License. Copyright 2012-2014 RubyMoney.
590
+ MIT License. Copyright 2021 RubyMoney.
data/Rakefile CHANGED
@@ -55,6 +55,13 @@ namespace :spec do
55
55
  framework, version = file_name.split(/(\d+)/)
56
56
  major, minor = version.split(//)
57
57
 
58
+ # Ruby 3 exclusions
59
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.0.0')
60
+ # Rails 5 does not support ruby-3.0.0 https://github.com/rails/rails/issues/40938#issuecomment-751569171
61
+ # Mongoid gem does not yet support ruby-3.0.0 https://github.com/mongodb/mongoid#compatibility
62
+ next if framework == 'mongoid' || (framework == 'rails' && version == "5")
63
+ end
64
+
58
65
  frameworks_versions[framework] ||= []
59
66
  frameworks_versions[framework] << file_name
60
67
 
@@ -0,0 +1,4 @@
1
+ da:
2
+ errors:
3
+ messages:
4
+ invalid_currency: skal være en gyldig valuta (f.eks. '100', '5%{decimal}24', eller '123%{thousands}456%{decimal}78'). Fik %{currency}
@@ -83,6 +83,29 @@ MoneyRails.configure do |config|
83
83
  # sign_before_symbol: nil
84
84
  # }
85
85
 
86
+ # If you would like to use I18n localization (formatting depends on the
87
+ # locale):
88
+ # config.locale_backend = :i18n
89
+ #
90
+ # Example (using default localization from rails-i18n):
91
+ #
92
+ # I18n.locale = :en
93
+ # Money.new(10_000_00, 'USD').format # => $10,000.00
94
+ # I18n.locale = :es
95
+ # Money.new(10_000_00, 'USD').format # => $10.000,00
96
+ #
97
+ # For the legacy behaviour of "per currency" localization (formatting depends
98
+ # only on currency):
99
+ # config.locale_backend = :currency
100
+ #
101
+ # Example:
102
+ # Money.new(10_000_00, 'USD').format # => $10,000.00
103
+ # Money.new(10_000_00, 'EUR').format # => €10.000,00
104
+ #
105
+ # In case you don't need localization and would like to use default values
106
+ # (can be redefined using config.default_format):
107
+ # config.locale_backend = nil
108
+
86
109
  # Set default raise_error_on_money_parsing option
87
110
  # It will be raise error if assigned different currency
88
111
  # The default value is false
@@ -1,113 +1,108 @@
1
1
  module MoneyRails
2
2
  module ActiveModel
3
3
  class MoneyValidator < ::ActiveModel::Validations::NumericalityValidator
4
- def validate_each(record, attr, _value)
5
- reset_memoized_variables!
6
- @record = record
7
- @attr = attr
4
+ class Details < Struct.new(:raw_value, :thousands_separator, :decimal_mark)
5
+ def abs_raw_value
6
+ @abs_raw_value ||= raw_value.to_s.sub(/^\s*-/, "").strip
7
+ end
8
+
9
+ def decimal_pieces
10
+ @decimal_pieces ||= abs_raw_value.split(decimal_mark)
11
+ end
12
+ end
8
13
 
9
- subunit_attr = @record.class.monetized_attributes[@attr.to_s]
14
+ def validate_each(record, attr, _value)
15
+ subunit_attr = record.class.monetized_attributes[attr.to_s]
16
+ currency = record.public_send("currency_for_#{attr}")
10
17
 
11
18
  # WARNING: Currently this is only defined in ActiveRecord extension!
12
- before_type_cast = :"#{@attr}_money_before_type_cast"
13
- @raw_value = @record.try(before_type_cast)
19
+ before_type_cast = :"#{attr}_money_before_type_cast"
20
+ raw_value = record.try(before_type_cast)
14
21
 
15
22
  # If raw value is nil and changed subunit is nil, then
16
23
  # nil is a assigned value, else we should treat the
17
24
  # subunit value as the one assigned.
18
- if @raw_value.nil? && @record.public_send(subunit_attr)
19
- subunit_value = @record.public_send(subunit_attr)
20
- @raw_value = subunit_value.to_f / currency.subunit_to_unit
25
+ if raw_value.nil? && record.public_send(subunit_attr)
26
+ subunit_value = record.public_send(subunit_attr)
27
+ raw_value = subunit_value.to_f / currency.subunit_to_unit
21
28
  end
22
29
 
23
- return if options[:allow_nil] && @raw_value.nil?
30
+ return if options[:allow_nil] && raw_value.nil?
24
31
 
25
- # Set this before we modify @raw_value below.
26
- stringy = @raw_value.present? && !@raw_value.is_a?(Numeric) && !@raw_value.is_a?(Money)
32
+ # Set this before we modify raw_value below.
33
+ stringy = raw_value.present? && !raw_value.is_a?(Numeric) && !raw_value.is_a?(Money)
27
34
 
28
35
  if stringy
36
+ # TODO: This is supporting legacy behaviour where a symbol can come from a i18n locale,
37
+ # however practical implications of that are most likely non-existent
38
+ symbol = lookup(:symbol, currency) || currency.symbol
39
+
29
40
  # remove currency symbol
30
- @raw_value = @raw_value.to_s.gsub(symbol, "")
41
+ raw_value = raw_value.to_s.gsub(symbol, "")
31
42
  end
32
43
 
33
- normalize_raw_value!
34
- super(@record, @attr, @raw_value)
35
-
36
- if stringy && record_does_not_have_error?
37
- add_error if
38
- value_has_too_many_decimal_points ||
39
- thousand_separator_after_decimal_mark ||
40
- invalid_thousands_separation
41
- end
42
- end
44
+ # Cache abs_raw_value before normalizing because it's used in
45
+ # many places and relies on the original raw_value.
46
+ details = generate_details(raw_value, currency)
47
+ normalized_raw_value = normalize(details)
43
48
 
44
- private
49
+ super(record, attr, normalized_raw_value)
45
50
 
46
- def record_does_not_have_error?
47
- return true unless @record.errors.has_key?(@attr)
48
- !@record.errors.added?(@attr, :not_a_number)
49
- end
51
+ return unless stringy
52
+ return if record_already_has_error?(record, attr, normalized_raw_value)
50
53
 
51
- def reset_memoized_variables!
52
- [:currency, :decimal_mark, :thousands_separator, :symbol,
53
- :abs_raw_value, :decimal_pieces, :pieces_array].each do |var_name|
54
- ivar_name = :"@_#{var_name}"
55
- remove_instance_variable(ivar_name) if instance_variable_defined?(ivar_name)
56
- end
54
+ add_error!(record, attr, details) if
55
+ value_has_too_many_decimal_points(details) ||
56
+ thousand_separator_after_decimal_mark(details) ||
57
+ invalid_thousands_separation(details)
57
58
  end
58
59
 
59
- def currency
60
- @_currency ||= @record.public_send("currency_for_#{@attr}")
61
- end
60
+ private
62
61
 
63
- def decimal_mark
64
- character = currency.decimal_mark || '.'
65
- @_decimal_mark ||= Money.use_i18n ? I18n.t('number.currency.format.separator', default: character) : character
66
- end
62
+ DEFAULTS = {
63
+ decimal_mark: '.',
64
+ thousands_separator: ','
65
+ }.freeze
67
66
 
68
- def thousands_separator
69
- character = currency.thousands_separator || ','
70
- @_thousands_separator ||= Money.use_i18n ? I18n.t('number.currency.format.delimiter', default: character) : character
71
- end
67
+ def generate_details(raw_value, currency)
68
+ thousands_separator = lookup(:thousands_separator, currency)
69
+ decimal_mark = lookup(:decimal_mark, currency)
72
70
 
73
- def symbol
74
- @_symbol ||= Money.use_i18n ? I18n.t('number.currency.format.unit', default: currency.symbol) : currency.symbol
71
+ Details.new(raw_value, thousands_separator, decimal_mark)
75
72
  end
76
73
 
77
- def abs_raw_value
78
- @_abs_raw_value ||= @raw_value.to_s.sub(/^\s*-/, "").strip
74
+ def record_already_has_error?(record, attr, raw_value)
75
+ record.errors.added?(attr, :not_a_number, value: raw_value)
79
76
  end
80
77
 
81
- def add_error
82
- attr_name = @attr.to_s.tr('.', '_').humanize
83
- attr_name = @record.class.human_attribute_name(@attr, default: attr_name)
78
+ def add_error!(record, attr, details)
79
+ attr_name = attr.to_s.tr('.', '_').humanize
80
+ attr_name = record.class.human_attribute_name(attr, default: attr_name)
84
81
 
85
- @record.errors.add(@attr, :invalid_currency,
86
- { thousands: thousands_separator,
87
- decimal: decimal_mark,
88
- currency: abs_raw_value,
89
- attribute: attr_name })
82
+ record.errors.add(attr, :invalid_currency,
83
+ thousands: details.thousands_separator,
84
+ decimal: details.decimal_mark,
85
+ currency: details.abs_raw_value,
86
+ attribute: attr_name
87
+ )
90
88
  end
91
89
 
92
- def decimal_pieces
93
- @_decimal_pieces ||= abs_raw_value.split(decimal_mark)
90
+ def value_has_too_many_decimal_points(details)
91
+ ![1, 2].include?(details.decimal_pieces.length)
94
92
  end
95
93
 
96
- def value_has_too_many_decimal_points
97
- ![1, 2].include?(decimal_pieces.length)
94
+ def thousand_separator_after_decimal_mark(details)
95
+ details.thousands_separator.present? &&
96
+ details.decimal_pieces.length == 2 &&
97
+ details.decimal_pieces[1].include?(details.thousands_separator)
98
98
  end
99
99
 
100
- def pieces_array
101
- @_pieces_array ||= decimal_pieces[0].split(thousands_separator.presence)
102
- end
100
+ def invalid_thousands_separation(details)
101
+ pieces_array = details.decimal_pieces[0].split(details.thousands_separator.presence)
103
102
 
104
- def thousand_separator_after_decimal_mark
105
- thousands_separator.present? && decimal_pieces.length == 2 && decimal_pieces[1].include?(thousands_separator)
106
- end
107
-
108
- def invalid_thousands_separation
109
103
  return false if pieces_array.length <= 1
110
104
  return true if pieces_array[0].length > 3
105
+
111
106
  pieces_array[1..-1].any? do |thousands_group|
112
107
  thousands_group.length != 3
113
108
  end
@@ -115,16 +110,25 @@ module MoneyRails
115
110
 
116
111
  # Remove thousands separators, normalize decimal mark,
117
112
  # remove whitespaces and _ (E.g. 99 999 999 or 12_300_200.20)
118
- def normalize_raw_value!
119
- # Cache abs_raw_value before normalizing because it's used in
120
- # many places and relies on the original @raw_value.
121
- abs_raw_value
122
-
123
- @raw_value = @raw_value.to_s
124
- .gsub(thousands_separator, '')
125
- .gsub(decimal_mark, '.')
113
+ def normalize(details)
114
+ details.raw_value
115
+ .to_s
116
+ .gsub(details.thousands_separator, '')
117
+ .gsub(details.decimal_mark, '.')
126
118
  .gsub(/[\s_]/, '')
127
119
  end
120
+
121
+ def lookup(key, currency)
122
+ if locale_backend
123
+ locale_backend.lookup(key, currency) || DEFAULTS[key]
124
+ else
125
+ DEFAULTS[key]
126
+ end
127
+ end
128
+
129
+ def locale_backend
130
+ Money.locale_backend
131
+ end
128
132
  end
129
133
  end
130
134
  end
@@ -5,14 +5,15 @@ module MoneyRails
5
5
  def add_monetize(table_name, accessor, options={})
6
6
  [:amount, :currency].each do |attribute|
7
7
  column_present, *opts = OptionsExtractor.extract attribute, table_name, accessor, options
8
- add_column(*opts) if column_present
8
+ constraints = opts.pop
9
+ add_column(*opts, **constraints) if column_present
9
10
  end
10
11
  end
11
12
 
12
13
  def remove_monetize(table_name, accessor, options={})
13
14
  [:amount, :currency].each do |attribute|
14
- column_present, table_name, column_name, _, _ = OptionsExtractor.extract attribute, table_name, accessor, options
15
- remove_column table_name, column_name if column_present
15
+ column_present, table_name, column_name, type, _ = OptionsExtractor.extract attribute, table_name, accessor, options
16
+ remove_column table_name, column_name, type if column_present
16
17
  end
17
18
  end
18
19
  end
@@ -5,7 +5,8 @@ module MoneyRails
5
5
  def monetize(accessor, options={})
6
6
  [:amount, :currency].each do |attribute|
7
7
  column_present, _, *opts = OptionsExtractor.extract attribute, :no_table, accessor, options
8
- column(*opts) if column_present
8
+ constraints = opts.pop
9
+ column(*opts, **constraints) if column_present
9
10
  end
10
11
  end
11
12
 
@@ -209,7 +209,7 @@ module MoneyRails
209
209
 
210
210
  if MoneyRails::Configuration.preserve_user_input
211
211
  value_before_type_cast = instance_variable_get "@#{name}_money_before_type_cast"
212
- if errors[name.to_sym].present?
212
+ if errors.has_key?(name.to_sym)
213
213
  result.define_singleton_method(:to_s) { value_before_type_cast }
214
214
  result.define_singleton_method(:format) { |_| value_before_type_cast }
215
215
  end
@@ -7,7 +7,6 @@ module MoneyRails
7
7
  # MoneyRails configuration module.
8
8
  # This is extended by MoneyRails to provide configuration settings.
9
9
  module Configuration
10
-
11
10
  # Start a MoneyRails configuration block in an initializer.
12
11
  #
13
12
  # example: Provide a default currency for the application
@@ -59,7 +58,7 @@ module MoneyRails
59
58
  # MoneyRails.configure do |config|
60
59
  # config.default_bank = EuCentralBank.new
61
60
  # end
62
- delegate :default_bank=, :default_bank, to: :Money
61
+ delegate :default_bank=, :default_bank, :locale_backend, :locale_backend=, to: :Money
63
62
 
64
63
  # Provide exchange rates
65
64
  delegate :add_rate, to: :Money