money 6.5.1 → 6.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +209 -5
  3. data/LICENSE +18 -16
  4. data/README.md +321 -70
  5. data/config/currency_backwards_compatible.json +65 -0
  6. data/config/currency_iso.json +280 -94
  7. data/config/currency_non_iso.json +101 -3
  8. data/lib/money/bank/base.rb +1 -3
  9. data/lib/money/bank/variable_exchange.rb +88 -96
  10. data/lib/money/currency/heuristics.rb +1 -143
  11. data/lib/money/currency/loader.rb +15 -13
  12. data/lib/money/currency.rb +98 -81
  13. data/lib/money/locale_backend/base.rb +7 -0
  14. data/lib/money/locale_backend/currency.rb +11 -0
  15. data/lib/money/locale_backend/errors.rb +6 -0
  16. data/lib/money/locale_backend/i18n.rb +25 -0
  17. data/lib/money/locale_backend/legacy.rb +28 -0
  18. data/lib/money/money/allocation.rb +46 -0
  19. data/lib/money/money/arithmetic.rb +97 -52
  20. data/lib/money/money/constructors.rb +5 -6
  21. data/lib/money/money/formatter.rb +399 -0
  22. data/lib/money/money/formatting_rules.rb +142 -0
  23. data/lib/money/money/locale_backend.rb +22 -0
  24. data/lib/money/money.rb +268 -194
  25. data/lib/money/rates_store/memory.rb +120 -0
  26. data/lib/money/version.rb +1 -1
  27. data/money.gemspec +15 -20
  28. metadata +36 -59
  29. data/.coveralls.yml +0 -1
  30. data/.gitignore +0 -22
  31. data/.travis.yml +0 -13
  32. data/AUTHORS +0 -116
  33. data/CONTRIBUTING.md +0 -17
  34. data/Gemfile +0 -7
  35. data/Rakefile +0 -17
  36. data/lib/money/money/formatting.rb +0 -386
  37. data/spec/bank/base_spec.rb +0 -77
  38. data/spec/bank/single_currency_spec.rb +0 -11
  39. data/spec/bank/variable_exchange_spec.rb +0 -275
  40. data/spec/currency/heuristics_spec.rb +0 -84
  41. data/spec/currency_spec.rb +0 -321
  42. data/spec/money/arithmetic_spec.rb +0 -568
  43. data/spec/money/constructors_spec.rb +0 -75
  44. data/spec/money/formatting_spec.rb +0 -667
  45. data/spec/money_spec.rb +0 -745
  46. data/spec/spec_helper.rb +0 -23
@@ -1,386 +0,0 @@
1
- # encoding: UTF-8
2
- class Money
3
- module Formatting
4
- def self.included(base)
5
- [
6
- [:thousands_separator, :delimiter, ","],
7
- [:decimal_mark, :separator, "."]
8
- ].each do |method, name, character|
9
- define_i18n_method(method, name, character)
10
- end
11
- end
12
-
13
- def self.define_i18n_method(method, name, character)
14
- define_method(method) do
15
- if self.class.use_i18n
16
- begin
17
- I18n.t name, :scope => "number.currency.format", :raise => true
18
- rescue I18n::MissingTranslationData => e
19
- I18n.t name, :scope =>"number.format", :default => (currency.send(method) || character)
20
- end
21
- else
22
- currency.send(method) || character
23
- end
24
- end
25
- alias_method name, method
26
- end
27
-
28
- # Creates a formatted price string according to several rules.
29
- #
30
- # @param [Hash] rules The options used to format the string.
31
- #
32
- # @return [String]
33
- #
34
- # @option *rules [Boolean, String] :display_free (false) Whether a zero
35
- # amount of money should be formatted of "free" or as the supplied string.
36
- #
37
- # @example
38
- # Money.us_dollar(0).format(:display_free => true) #=> "free"
39
- # Money.us_dollar(0).format(:display_free => "gratis") #=> "gratis"
40
- # Money.us_dollar(0).format #=> "$0.00"
41
- #
42
- # @option *rules [Boolean] :with_currency (false) Whether the currency name
43
- # should be appended to the result string.
44
- #
45
- # @example
46
- # Money.ca_dollar(100).format #=> "$1.00"
47
- # Money.ca_dollar(100).format(:with_currency => true) #=> "$1.00 CAD"
48
- # Money.us_dollar(85).format(:with_currency => true) #=> "$0.85 USD"
49
- #
50
- # @option *rules [Boolean] :rounded_infinite_precision (false) Whether the
51
- # amount of money should be rounded when using infinite_precision
52
- #
53
- # @example
54
- # Money.us_dollar(100.1).format #=> "$1.001"
55
- # Money.us_dollar(100.1).format(:rounded_infinite_precision => true) #=> "$1"
56
- # Money.us_dollar(100.9).format(:rounded_infinite_precision => true) #=> "$1.01"
57
- #
58
- # @option *rules [Boolean] :no_cents (false) Whether cents should be omitted.
59
- #
60
- # @example
61
- # Money.ca_dollar(100).format(:no_cents => true) #=> "$1"
62
- # Money.ca_dollar(599).format(:no_cents => true) #=> "$5"
63
- #
64
- # @option *rules [Boolean] :no_cents_if_whole (false) Whether cents should be
65
- # omitted if the cent value is zero
66
- #
67
- # @example
68
- # Money.ca_dollar(10000).format(:no_cents_if_whole => true) #=> "$100"
69
- # Money.ca_dollar(10034).format(:no_cents_if_whole => true) #=> "$100.34"
70
- #
71
- # @option *rules [Boolean, String, nil] :symbol (true) Whether a money symbol
72
- # should be prepended to the result string. The default is true. This method
73
- # attempts to pick a symbol that's suitable for the given currency.
74
- #
75
- # @example
76
- # Money.new(100, "USD") #=> "$1.00"
77
- # Money.new(100, "GBP") #=> "£1.00"
78
- # Money.new(100, "EUR") #=> "€1.00"
79
- #
80
- # # Same thing.
81
- # Money.new(100, "USD").format(:symbol => true) #=> "$1.00"
82
- # Money.new(100, "GBP").format(:symbol => true) #=> "£1.00"
83
- # Money.new(100, "EUR").format(:symbol => true) #=> "€1.00"
84
- #
85
- # # You can specify a false expression or an empty string to disable
86
- # # prepending a money symbol.§
87
- # Money.new(100, "USD").format(:symbol => false) #=> "1.00"
88
- # Money.new(100, "GBP").format(:symbol => nil) #=> "1.00"
89
- # Money.new(100, "EUR").format(:symbol => "") #=> "1.00"
90
- #
91
- # # If the symbol for the given currency isn't known, then it will default
92
- # # to "¤" as symbol.
93
- # Money.new(100, "AWG").format(:symbol => true) #=> "¤1.00"
94
- #
95
- # # You can specify a string as value to enforce using a particular symbol.
96
- # Money.new(100, "AWG").format(:symbol => "ƒ") #=> "ƒ1.00"
97
- #
98
- # # You can specify a indian currency format
99
- # Money.new(10000000, "INR").format(:south_asian_number_formatting => true) #=> "1,00,000.00"
100
- # Money.new(10000000).format(:south_asian_number_formatting => true) #=> "$1,00,000.00"
101
- #
102
- # @option *rules [Boolean, nil] :symbol_before_without_space (true) Whether
103
- # a space between the money symbol and the amount should be inserted when
104
- # +:symbol_position+ is +:before+. The default is true (meaning no space). Ignored
105
- # if +:symbol+ is false or +:symbol_position+ is not +:before+.
106
- #
107
- # @example
108
- # # Default is to not insert a space.
109
- # Money.new(100, "USD").format #=> "$1.00"
110
- #
111
- # # Same thing.
112
- # Money.new(100, "USD").format(:symbol_before_without_space => true) #=> "$1.00"
113
- #
114
- # # If set to false, will insert a space.
115
- # Money.new(100, "USD").format(:symbol_before_without_space => false) #=> "$ 1.00"
116
- #
117
- # @option *rules [Boolean, nil] :symbol_after_without_space (false) Whether
118
- # a space between the the amount and the money symbol should be inserted when
119
- # +:symbol_position+ is +:after+. The default is false (meaning space). Ignored
120
- # if +:symbol+ is false or +:symbol_position+ is not +:after+.
121
- #
122
- # @example
123
- # # Default is to insert a space.
124
- # Money.new(100, "USD").format(:symbol_position => :after) #=> "1.00 $"
125
- #
126
- # # If set to true, will not insert a space.
127
- # Money.new(100, "USD").format(:symbol_position => :after, :symbol_after_without_space => true) #=> "1.00$"
128
- #
129
- # @option *rules [Boolean, String, nil] :decimal_mark (true) Whether the
130
- # currency should be separated by the specified character or '.'
131
- #
132
- # @example
133
- # # If a string is specified, it's value is used.
134
- # Money.new(100, "USD").format(:decimal_mark => ",") #=> "$1,00"
135
- #
136
- # # If the decimal_mark for a given currency isn't known, then it will default
137
- # # to "." as decimal_mark.
138
- # Money.new(100, "FOO").format #=> "$1.00"
139
- #
140
- # @option *rules [Boolean, String, nil] :thousands_separator (true) Whether
141
- # the currency should be delimited by the specified character or ','
142
- #
143
- # @example
144
- # # If false is specified, no thousands_separator is used.
145
- # Money.new(100000, "USD").format(:thousands_separator => false) #=> "1000.00"
146
- # Money.new(100000, "USD").format(:thousands_separator => nil) #=> "1000.00"
147
- # Money.new(100000, "USD").format(:thousands_separator => "") #=> "1000.00"
148
- #
149
- # # If a string is specified, it's value is used.
150
- # Money.new(100000, "USD").format(:thousands_separator => ".") #=> "$1.000.00"
151
- #
152
- # # If the thousands_separator for a given currency isn't known, then it will
153
- # # default to "," as thousands_separator.
154
- # Money.new(100000, "FOO").format #=> "$1,000.00"
155
- #
156
- # @option *rules [Boolean] :html (false) Whether the currency should be
157
- # HTML-formatted. Only useful in combination with +:with_currency+.
158
- #
159
- # @example
160
- # s = Money.ca_dollar(570).format(:html => true, :with_currency => true)
161
- # s #=> "$5.70 <span class=\"currency\">CAD</span>"
162
- #
163
- # @option *rules [Boolean] :sign_before_symbol (false) Whether the sign should be
164
- # before the currency symbol.
165
- #
166
- # @example
167
- # # You can specify to display the sign before the symbol for negative numbers
168
- # Money.new(-100, "GBP").format(:sign_before_symbol => true) #=> "-£1.00"
169
- # Money.new(-100, "GBP").format(:sign_before_symbol => false) #=> "£-1.00"
170
- # Money.new(-100, "GBP").format #=> "£-1.00"
171
- #
172
- # @option *rules [Boolean] :sign_positive (false) Whether positive numbers should be
173
- # signed, too.
174
- #
175
- # @example
176
- # # You can specify to display the sign with positive numbers
177
- # Money.new(100, "GBP").format(:sign_positive => true, :sign_before_symbol => true) #=> "+£1.00"
178
- # Money.new(100, "GBP").format(:sign_positive => true, :sign_before_symbol => false) #=> "£+1.00"
179
- # Money.new(100, "GBP").format(:sign_positive => false, :sign_before_symbol => true) #=> "£1.00"
180
- # Money.new(100, "GBP").format(:sign_positive => false, :sign_before_symbol => false) #=> "£1.00"
181
- # Money.new(100, "GBP").format #=> "£+1.00"
182
- #
183
- # @option *rules [Boolean] :disambiguate (false) Prevents the result from being ambiguous
184
- # due to equal symbols for different currencies. Uses the `disambiguate_symbol`.
185
- #
186
- # @example
187
- # Money.new(100, "USD").format(:disambiguate => false) #=> "$100.00"
188
- # Money.new(100, "CAD").format(:disambiguate => false) #=> "$100.00"
189
- # Money.new(100, "USD").format(:disambiguate => true) #=> "$100.00"
190
- # Money.new(100, "CAD").format(:disambiguate => true) #=> "C$100.00"
191
- #
192
- # @option *rules [Boolean] :html_wrap_symbol (false) Wraps the currency symbol
193
- # in a html <span> tag.
194
- #
195
- # @example
196
- # Money.new(100, "USD").format(:disambiguate => false)
197
- # #=> "<span class=\"currency_symbol\">$100.00</span>
198
- #
199
- # @option *rules [Symbol] :symbol_position (:before) `:before` if the currency
200
- # symbol goes before the amount, `:after` if it goes after.
201
- #
202
- # @example
203
- # Money.new(100, "USD").format(:symbol_position => :before) #=> "$100.00"
204
- # Money.new(100, "USD").format(:symbol_position => :after) #=> "100.00 $"
205
- #
206
- # Note that the default rules can be defined through +Money.default_formatting_rules+ hash.
207
- #
208
- # @see +Money.default_formatting_rules+ for more information.
209
- def format(*rules)
210
- # support for old format parameters
211
- rules = normalize_formatting_rules(rules)
212
-
213
- rules = default_formatting_rules.merge(rules)
214
- rules = localize_formatting_rules(rules)
215
-
216
- if fractional == 0
217
- if rules[:display_free].respond_to?(:to_str)
218
- return rules[:display_free]
219
- elsif rules[:display_free]
220
- return "free"
221
- end
222
- end
223
-
224
- symbol_value = symbol_value_from(rules)
225
-
226
- formatted = self.abs.to_s
227
-
228
- if rules[:rounded_infinite_precision]
229
- formatted.gsub!(/#{currency.decimal_mark}/, '.') unless '.' == currency.decimal_mark
230
- formatted = ((BigDecimal(formatted) * currency.subunit_to_unit).round / BigDecimal(currency.subunit_to_unit.to_s)).to_s("F")
231
- formatted.gsub!(/\..*/) do |decimal_part|
232
- decimal_part << '0' while decimal_part.length < (currency.decimal_places + 1)
233
- decimal_part
234
- end
235
- formatted.gsub!(/\./, currency.decimal_mark) unless '.' == currency.decimal_mark
236
- end
237
-
238
- sign = self.negative? ? '-' : ''
239
-
240
- if rules[:no_cents] || (rules[:no_cents_if_whole] && cents % currency.subunit_to_unit == 0)
241
- formatted = "#{formatted.to_i}"
242
- end
243
-
244
- thousands_separator_value = thousands_separator
245
- # Determine thousands_separator
246
- if rules.has_key?(:thousands_separator)
247
- thousands_separator_value = rules[:thousands_separator] || ''
248
- end
249
-
250
- # Apply thousands_separator
251
- formatted.gsub!(regexp_format(formatted, rules, decimal_mark, symbol_value),
252
- "\\1#{thousands_separator_value}")
253
-
254
- symbol_position = symbol_position_from(rules)
255
-
256
- if rules[:sign_positive] == true && self.positive?
257
- sign = '+'
258
- end
259
-
260
- if rules[:sign_before_symbol] == true
261
- sign_before = sign
262
- sign = ''
263
- end
264
-
265
- if symbol_value && !symbol_value.empty?
266
- symbol_value = "<span class=\"currency_symbol\">#{symbol_value}</span>" if rules[:html_wrap_symbol]
267
-
268
- formatted = if symbol_position == :before
269
- symbol_space = rules[:symbol_before_without_space] === false ? " " : ""
270
- "#{sign_before}#{symbol_value}#{symbol_space}#{sign}#{formatted}"
271
- else
272
- symbol_space = rules[:symbol_after_without_space] ? "" : " "
273
- "#{sign_before}#{sign}#{formatted}#{symbol_space}#{symbol_value}"
274
- end
275
- else
276
- formatted="#{sign_before}#{sign}#{formatted}"
277
- end
278
-
279
- apply_decimal_mark_from_rules(formatted, rules)
280
-
281
- if rules[:with_currency]
282
- formatted << " "
283
- formatted << '<span class="currency">' if rules[:html]
284
- formatted << currency.to_s
285
- formatted << '</span>' if rules[:html]
286
- end
287
- formatted
288
- end
289
-
290
- private
291
-
292
- # Cleans up formatting rules.
293
- #
294
- # @param [Hash] rules
295
- #
296
- # @return [Hash]
297
- def normalize_formatting_rules(rules)
298
- if rules.size == 0
299
- rules = {}
300
- elsif rules.size == 1
301
- rules = rules.pop
302
- rules = { rules => true } if rules.is_a?(Symbol)
303
- end
304
- if !rules.include?(:decimal_mark) && rules.include?(:separator)
305
- rules[:decimal_mark] = rules[:separator]
306
- end
307
- if !rules.include?(:thousands_separator) && rules.include?(:delimiter)
308
- rules[:thousands_separator] = rules[:delimiter]
309
- end
310
- rules
311
- end
312
-
313
- # Applies decimal mark from rules to formatted
314
- #
315
- # @param [String] formatted
316
- # @param [Hash] rules
317
- def apply_decimal_mark_from_rules(formatted, rules)
318
- if rules.has_key?(:decimal_mark) && rules[:decimal_mark] &&
319
- rules[:decimal_mark] != decimal_mark
320
-
321
- regexp_decimal = Regexp.escape(decimal_mark)
322
- formatted.sub!(/(.*)(#{regexp_decimal})(.*)\Z/,
323
- "\\1#{rules[:decimal_mark]}\\3")
324
- end
325
- end
326
- end
327
-
328
- def default_formatting_rules
329
- self.class.default_formatting_rules || {}
330
- end
331
-
332
- def regexp_format(formatted, rules, decimal_mark, symbol_value)
333
- regexp_decimal = Regexp.escape(decimal_mark)
334
- if rules[:south_asian_number_formatting]
335
- /(\d+?)(?=(\d\d)+(\d)(?:\.))/
336
- else
337
- # Symbols may contain decimal marks (E.g "դր.")
338
- if formatted.sub(symbol_value.to_s, "") =~ /#{regexp_decimal}/
339
- /(\d)(?=(?:\d{3})+(?:#{regexp_decimal}))/
340
- else
341
- /(\d)(?=(?:\d{3})+(?:[^\d]{1}|$))/
342
- end
343
- end
344
- end
345
-
346
- def localize_formatting_rules(rules)
347
- if currency.iso_code == "JPY" && I18n.locale == :ja
348
- rules[:symbol] = "円" unless rules[:symbol] == false
349
- rules[:symbol_position] = :after
350
- rules[:symbol_after_without_space] = true
351
- end
352
- rules
353
- end
354
-
355
- def symbol_value_from(rules)
356
- if rules.has_key?(:symbol)
357
- if rules[:symbol] === true
358
- symbol
359
- elsif rules[:symbol]
360
- rules[:symbol]
361
- else
362
- ""
363
- end
364
- elsif rules[:html]
365
- currency.html_entity == '' ? currency.symbol : currency.html_entity
366
- elsif rules[:disambiguate] and currency.disambiguate_symbol
367
- currency.disambiguate_symbol
368
- else
369
- symbol
370
- end
371
- end
372
-
373
- def symbol_position_from(rules)
374
- if rules.has_key?(:symbol_position)
375
- if [:before, :after].include?(rules[:symbol_position])
376
- return rules[:symbol_position]
377
- else
378
- raise ArgumentError, ":symbol_position must be ':before' or ':after'"
379
- end
380
- elsif currency.symbol_first?
381
- :before
382
- else
383
- :after
384
- end
385
- end
386
- end
@@ -1,77 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Money::Bank::Base do
4
-
5
- describe ".instance" do
6
- it "is local to one class" do
7
- klass = Money::Bank::Base
8
- subclass = Class.new(Money::Bank::Base)
9
- expect(klass.instance).not_to eq subclass.instance
10
- end
11
- end
12
-
13
- describe "#initialize" do
14
- it "accepts a block and stores @rounding_method" do
15
- proc = Proc.new { |n| n.ceil }
16
- bank = Money::Bank::Base.new(&proc)
17
- expect(bank.rounding_method).to eq proc
18
- end
19
- end
20
-
21
- describe "#setup" do
22
- it "calls #setup after #initialize" do
23
- class MyBank < Money::Bank::Base
24
- attr_reader :setup_called
25
-
26
- def setup
27
- @setup_called = true
28
- end
29
- end
30
-
31
- bank = MyBank.new
32
- expect(bank.setup_called).to eq true
33
- end
34
- end
35
-
36
- describe "#exchange_with" do
37
- it "is not implemented" do
38
- expect { subject.exchange_with(Money.new(100, 'USD'), 'EUR') }.to raise_exception(NotImplementedError)
39
- end
40
- end
41
-
42
- describe "#same_currency?" do
43
- it "accepts str/str" do
44
- expect { subject.send(:same_currency?, 'USD', 'EUR') }.to_not raise_exception
45
- end
46
-
47
- it "accepts currency/str" do
48
- expect { subject.send(:same_currency?, Money::Currency.wrap('USD'), 'EUR') }.to_not raise_exception
49
- end
50
-
51
- it "accepts str/currency" do
52
- expect { subject.send(:same_currency?, 'USD', Money::Currency.wrap('EUR')) }.to_not raise_exception
53
- end
54
-
55
- it "accepts currency/currency" do
56
- expect { subject.send(:same_currency?, Money::Currency.wrap('USD'), Money::Currency.wrap('EUR')) }.to_not raise_exception
57
- end
58
-
59
- it "returns true when currencies match" do
60
- expect(subject.send(:same_currency?, 'USD', 'USD')).to be true
61
- expect(subject.send(:same_currency?, Money::Currency.wrap('USD'), 'USD')).to be true
62
- expect(subject.send(:same_currency?, 'USD', Money::Currency.wrap('USD'))).to be true
63
- expect(subject.send(:same_currency?, Money::Currency.wrap('USD'), Money::Currency.wrap('USD'))).to be true
64
- end
65
-
66
- it "returns false when currencies do not match" do
67
- expect(subject.send(:same_currency?, 'USD', 'EUR')).to be false
68
- expect(subject.send(:same_currency?, Money::Currency.wrap('USD'), 'EUR')).to be false
69
- expect(subject.send(:same_currency?, 'USD', Money::Currency.wrap('EUR'))).to be false
70
- expect(subject.send(:same_currency?, Money::Currency.wrap('USD'), Money::Currency.wrap('EUR'))).to be false
71
- end
72
-
73
- it "raises an UnknownCurrency exception when an unknown currency is passed" do
74
- expect { subject.send(:same_currency?, 'AAA', 'BBB') }.to raise_exception(Money::Currency::UnknownCurrency)
75
- end
76
- end
77
- end
@@ -1,11 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Money::Bank::SingleCurrency do
4
- describe "#exchange_with" do
5
- it "raises when called" do
6
- expect {
7
- subject.exchange_with(Money.new(100, 'USD'), 'EUR')
8
- }.to raise_exception(Money::Bank::DifferentCurrencyError, "No exchanging of currencies allowed: 1.00 USD to EUR")
9
- end
10
- end
11
- end