money 6.0.1.beta2 → 6.0.1.beta3
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.
- checksums.yaml +4 -4
- data/lib/money/bank/variable_exchange.rb +67 -28
- data/lib/money/currency.rb +69 -108
- data/lib/money/currency/heuristics.rb +121 -117
- data/lib/money/currency/loader.rb +19 -17
- data/lib/money/money.rb +81 -85
- data/lib/money/money/arithmetic.rb +42 -31
- data/lib/money/money/formatting.rb +45 -52
- data/lib/money/version.rb +1 -1
- data/spec/bank/variable_exchange_spec.rb +42 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf2817efb2d831c4dc20a21ad35e2668ef18f877
|
4
|
+
data.tar.gz: ab6fb4c372bae304d46f672517844816e2d9788e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6a4ce82b5f4d9624d9b1568a39357b0d35d190f2c6b94f30bce534f2b2daffa103e40c9da450788c36c2d994e33eeb1554eb1931655c29fa0cd474ceb971402
|
7
|
+
data.tar.gz: f08fda3dc489e7031a6937c28a32678bdef279b8e0e8725c832019576f380da9ee4b217aa6a5d7055985dcdff49cec1d6e48117a0e49843ad5792683290356aa
|
@@ -30,7 +30,7 @@ class Money
|
|
30
30
|
# bank.exchange_with(c2, "USD") #=> #<Money @fractional=803115>
|
31
31
|
class VariableExchange < Base
|
32
32
|
|
33
|
-
attr_reader :rates
|
33
|
+
attr_reader :rates, :mutex
|
34
34
|
|
35
35
|
# Available formats for importing/exporting rates.
|
36
36
|
RATE_FORMATS = [:json, :ruby, :yaml]
|
@@ -84,27 +84,38 @@ class Money
|
|
84
84
|
#
|
85
85
|
# # Exchange 100 CAD to USD:
|
86
86
|
# bank.exchange_with(c2, "USD") #=> #<Money @fractional=803115>
|
87
|
-
def exchange_with(from, to_currency)
|
88
|
-
|
87
|
+
def exchange_with(from, to_currency, &block)
|
88
|
+
to_currency = Currency.wrap(to_currency)
|
89
|
+
if from.currency == to_currency
|
90
|
+
from
|
91
|
+
else
|
92
|
+
if rate = get_rate(from.currency, to_currency)
|
93
|
+
fractional = calculate_fractional(from, to_currency)
|
94
|
+
Money.new(
|
95
|
+
exchange(fractional, rate, &block), to_currency
|
96
|
+
)
|
97
|
+
else
|
98
|
+
raise UnknownRate, "No conversion rate known for '#{from.currency.iso_code}' -> '#{to_currency}'"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def calculate_fractional(from, to_currency)
|
104
|
+
BigDecimal.new(from.fractional.to_s) / (
|
105
|
+
BigDecimal.new(from.currency.subunit_to_unit.to_s) /
|
106
|
+
BigDecimal.new(to_currency.subunit_to_unit.to_s)
|
107
|
+
)
|
108
|
+
end
|
89
109
|
|
90
|
-
|
91
|
-
|
92
|
-
|
110
|
+
def exchange(fractional, rate, &block)
|
111
|
+
ex = (fractional * BigDecimal.new(rate.to_s)).to_f
|
112
|
+
if block_given?
|
113
|
+
yield ex
|
114
|
+
elsif @rounding_method
|
115
|
+
@rounding_method.call(ex)
|
116
|
+
else
|
117
|
+
ex.to_s.to_i
|
93
118
|
end
|
94
|
-
_to_currency_ = Currency.wrap(to_currency)
|
95
|
-
|
96
|
-
fractional = BigDecimal.new(from.fractional.to_s) / (BigDecimal.new(from.currency.subunit_to_unit.to_s) / BigDecimal.new(_to_currency_.subunit_to_unit.to_s))
|
97
|
-
|
98
|
-
ex = fractional * BigDecimal.new(rate.to_s)
|
99
|
-
ex = ex.to_f
|
100
|
-
ex = if block_given?
|
101
|
-
yield ex
|
102
|
-
elsif @rounding_method
|
103
|
-
@rounding_method.call(ex)
|
104
|
-
else
|
105
|
-
ex.to_s.to_i
|
106
|
-
end
|
107
|
-
Money.new(ex, _to_currency_)
|
108
119
|
end
|
109
120
|
|
110
121
|
# Registers a conversion rate and returns it (uses +#set_rate+).
|
@@ -129,6 +140,8 @@ class Money
|
|
129
140
|
# @param [Currency, String, Symbol] from Currency to exchange from.
|
130
141
|
# @param [Currency, String, Symbol] to Currency to exchange to.
|
131
142
|
# @param [Numeric] rate Rate to use when exchanging currencies.
|
143
|
+
# @param [Hash] opts Options hash to set special parameters
|
144
|
+
# @option opts [Boolean] :without_mutex disables the usage of a mutex
|
132
145
|
#
|
133
146
|
# @return [Numeric]
|
134
147
|
#
|
@@ -136,8 +149,13 @@ class Money
|
|
136
149
|
# bank = Money::Bank::VariableExchange.new
|
137
150
|
# bank.set_rate("USD", "CAD", 1.24515)
|
138
151
|
# bank.set_rate("CAD", "USD", 0.803115)
|
139
|
-
def set_rate(from, to, rate)
|
140
|
-
|
152
|
+
def set_rate(from, to, rate, opts = {})
|
153
|
+
fn = -> { @rates[rate_key_for(from, to)] = rate }
|
154
|
+
if opts[:without_mutex]
|
155
|
+
fn.call
|
156
|
+
else
|
157
|
+
@mutex.synchronize { fn.call }
|
158
|
+
end
|
141
159
|
end
|
142
160
|
|
143
161
|
# Retrieve the rate for the given currencies. Uses +Mutex+ to synchronize
|
@@ -145,6 +163,8 @@ class Money
|
|
145
163
|
#
|
146
164
|
# @param [Currency, String, Symbol] from Currency to exchange from.
|
147
165
|
# @param [Currency, String, Symbol] to Currency to exchange to.
|
166
|
+
# @param [Hash] opts Options hash to set special parameters
|
167
|
+
# @option opts [Boolean] :without_mutex disables the usage of a mutex
|
148
168
|
#
|
149
169
|
# @return [Numeric]
|
150
170
|
#
|
@@ -155,8 +175,13 @@ class Money
|
|
155
175
|
#
|
156
176
|
# bank.get_rate("USD", "CAD") #=> 1.24515
|
157
177
|
# bank.get_rate("CAD", "USD") #=> 0.803115
|
158
|
-
def get_rate(from, to)
|
159
|
-
|
178
|
+
def get_rate(from, to, opts = {})
|
179
|
+
fn = -> { @rates[rate_key_for(from, to)] }
|
180
|
+
if opts[:without_mutex]
|
181
|
+
fn.call
|
182
|
+
else
|
183
|
+
@mutex.synchronize { fn.call }
|
184
|
+
end
|
160
185
|
end
|
161
186
|
|
162
187
|
# Return the known rates as a string in the format specified. If +file+
|
@@ -165,6 +190,8 @@ class Money
|
|
165
190
|
#
|
166
191
|
# @param [Symbol] format Request format for the resulting string.
|
167
192
|
# @param [String] file Optional file location to write the rates to.
|
193
|
+
# @param [Hash] opts Options hash to set special parameters
|
194
|
+
# @option opts [Boolean] :without_mutex disables the usage of a mutex
|
168
195
|
#
|
169
196
|
# @return [String]
|
170
197
|
#
|
@@ -177,12 +204,12 @@ class Money
|
|
177
204
|
#
|
178
205
|
# s = bank.export_rates(:json)
|
179
206
|
# s #=> "{\"USD_TO_CAD\":1.24515,\"CAD_TO_USD\":0.803115}"
|
180
|
-
def export_rates(format, file=nil)
|
207
|
+
def export_rates(format, file = nil, opts = {})
|
181
208
|
raise Money::Bank::UnknownRateFormat unless
|
182
209
|
RATE_FORMATS.include? format
|
183
210
|
|
184
211
|
s = ""
|
185
|
-
|
212
|
+
fn = -> {
|
186
213
|
s = case format
|
187
214
|
when :json
|
188
215
|
JSON.dump(@rates)
|
@@ -196,6 +223,11 @@ class Money
|
|
196
223
|
File.open(file, "w") {|f| f.write(s) }
|
197
224
|
end
|
198
225
|
}
|
226
|
+
if opts[:without_mutex]
|
227
|
+
fn.call
|
228
|
+
else
|
229
|
+
@mutex.synchronize { fn.call }
|
230
|
+
end
|
199
231
|
s
|
200
232
|
end
|
201
233
|
|
@@ -204,6 +236,8 @@ class Money
|
|
204
236
|
#
|
205
237
|
# @param [Symbol] format The format of +s+.
|
206
238
|
# @param [String] s The rates string.
|
239
|
+
# @param [Hash] opts Options hash to set special parameters
|
240
|
+
# @option opts [Boolean] :without_mutex disables the usage of a mutex
|
207
241
|
#
|
208
242
|
# @return [self]
|
209
243
|
#
|
@@ -216,11 +250,11 @@ class Money
|
|
216
250
|
#
|
217
251
|
# bank.get_rate("USD", "CAD") #=> 1.24515
|
218
252
|
# bank.get_rate("CAD", "USD") #=> 0.803115
|
219
|
-
def import_rates(format, s)
|
253
|
+
def import_rates(format, s, opts = {})
|
220
254
|
raise Money::Bank::UnknownRateFormat unless
|
221
255
|
RATE_FORMATS.include? format
|
222
256
|
|
223
|
-
|
257
|
+
fn = -> {
|
224
258
|
@rates = case format
|
225
259
|
when :json
|
226
260
|
JSON.load(s)
|
@@ -230,6 +264,11 @@ class Money
|
|
230
264
|
YAML.load(s)
|
231
265
|
end
|
232
266
|
}
|
267
|
+
if opts[:without_mutex]
|
268
|
+
fn.call
|
269
|
+
else
|
270
|
+
@mutex.synchronize { fn.call }
|
271
|
+
end
|
233
272
|
self
|
234
273
|
end
|
235
274
|
|
data/lib/money/currency.rb
CHANGED
@@ -1,20 +1,19 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require
|
3
|
+
require "json"
|
4
|
+
require "money/currency/loader"
|
5
|
+
require "money/currency/heuristics"
|
4
6
|
|
5
7
|
class Money
|
6
8
|
|
7
9
|
# Represents a specific currency unit.
|
8
10
|
#
|
9
11
|
# @see http://en.wikipedia.org/wiki/Currency
|
12
|
+
# @see http://iso4217.net/
|
10
13
|
class Currency
|
11
14
|
include Comparable
|
12
|
-
|
13
|
-
|
14
|
-
extend Loader
|
15
|
-
|
16
|
-
require "money/currency/heuristics"
|
17
|
-
extend Heuristics
|
15
|
+
extend Money::Currency::Loader
|
16
|
+
extend Money::Currency::Heuristics
|
18
17
|
|
19
18
|
# Thrown when an unknown currency is requested.
|
20
19
|
class UnknownCurrency < StandardError; end
|
@@ -34,7 +33,9 @@ class Money
|
|
34
33
|
# Money::Currency.find(:foo) #=> nil
|
35
34
|
def find(id)
|
36
35
|
id = id.to_s.downcase.to_sym
|
37
|
-
new(id)
|
36
|
+
new(id)
|
37
|
+
rescue UnknownCurrency
|
38
|
+
nil
|
38
39
|
end
|
39
40
|
|
40
41
|
# Lookup a currency with given +num+ as an ISO 4217 numeric and returns an
|
@@ -51,7 +52,9 @@ class Money
|
|
51
52
|
def find_by_iso_numeric(num)
|
52
53
|
num = num.to_s
|
53
54
|
id, _ = self.table.find{|key, currency| currency[:iso_numeric] == num}
|
54
|
-
new(id)
|
55
|
+
new(id)
|
56
|
+
rescue UnknownCurrency
|
57
|
+
nil
|
55
58
|
end
|
56
59
|
|
57
60
|
# Wraps the object in a +Currency+ unless it's already a +Currency+
|
@@ -68,9 +71,7 @@ class Money
|
|
68
71
|
# Money::Currency.wrap(c1) #=> #<Money::Currency id: usd ...>
|
69
72
|
# Money::Currency.wrap("usd") #=> #<Money::Currency id: usd ...>
|
70
73
|
def wrap(object)
|
71
|
-
if object.nil?
|
72
|
-
nil
|
73
|
-
elsif object.is_a?(Currency)
|
74
|
+
if object.nil? || object.is_a?(Currency)
|
74
75
|
object
|
75
76
|
else
|
76
77
|
Currency.new(object)
|
@@ -128,71 +129,33 @@ class Money
|
|
128
129
|
end
|
129
130
|
end
|
130
131
|
|
131
|
-
# The symbol used to identify the currency,
|
132
|
-
# +iso_code+ attribute.
|
133
|
-
#
|
134
|
-
#
|
135
|
-
attr_reader
|
136
|
-
|
137
|
-
#
|
138
|
-
#
|
139
|
-
# @
|
140
|
-
attr_reader
|
141
|
-
|
142
|
-
#
|
143
|
-
#
|
144
|
-
#
|
145
|
-
# @
|
146
|
-
|
147
|
-
|
148
|
-
#
|
149
|
-
#
|
150
|
-
#
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
attr_reader :name
|
159
|
-
|
160
|
-
# The currency symbol (UTF-8 encoded).
|
161
|
-
#
|
162
|
-
# @return [String]
|
163
|
-
attr_reader :symbol
|
164
|
-
|
165
|
-
# The html entity for the currency symbol
|
166
|
-
#
|
167
|
-
# @return [String]
|
168
|
-
attr_reader :html_entity
|
169
|
-
|
170
|
-
# The name of the fractional monetary unit.
|
171
|
-
#
|
172
|
-
# @return [String]
|
173
|
-
attr_reader :subunit
|
174
|
-
|
175
|
-
# The proportion between the unit and the subunit
|
176
|
-
#
|
177
|
-
# @return [Integer]
|
178
|
-
attr_reader :subunit_to_unit
|
179
|
-
|
180
|
-
# The decimal mark, or character used to separate the whole unit from the subunit.
|
181
|
-
#
|
182
|
-
# @return [String]
|
183
|
-
attr_reader :decimal_mark
|
184
|
-
alias :separator :decimal_mark
|
185
|
-
|
186
|
-
# The character used to separate thousands grouping of the whole unit.
|
187
|
-
#
|
188
|
-
# @return [String]
|
189
|
-
attr_reader :thousands_separator
|
190
|
-
alias :delimiter :thousands_separator
|
191
|
-
|
192
|
-
# Should the currency symbol precede the amount, or should it come after?
|
193
|
-
#
|
194
|
-
# @return [Boolean]
|
195
|
-
attr_reader :symbol_first
|
132
|
+
# @attr_reader [Symbol] id The symbol used to identify the currency,
|
133
|
+
# usually the lowercase +iso_code+ attribute.
|
134
|
+
# @attr_reader [Integer] priority A numerical value you can use to
|
135
|
+
# sort/group the currency list.
|
136
|
+
# @attr_reader [String] iso_code The international 3-letter code as defined
|
137
|
+
# by the ISO 4217 standard.
|
138
|
+
# @attr_reader [String] iso_numeric The international 3-numeric code as
|
139
|
+
# defined by the ISO 4217 standard.
|
140
|
+
# @attr_reader [String] name The currency name.
|
141
|
+
# @attr_reader [String] symbol The currency symbol (UTF-8 encoded).
|
142
|
+
# @attr_reader [String] html_entity The html entity for the currency symbol
|
143
|
+
# @attr_reader [String] subunit The name of the fractional monetary unit.
|
144
|
+
# @attr_reader [Integer] subunit_to_unit The proportion between the unit
|
145
|
+
# and the subunit
|
146
|
+
# @attr_reader [String] decimal_mark The decimal mark, or character used to
|
147
|
+
# separate the whole unit from the subunit.
|
148
|
+
# @attr_reader [String] The character used to separate thousands grouping
|
149
|
+
# of the whole unit.
|
150
|
+
# @attr_reader [Boolean] symbol_first Should the currency symbol precede
|
151
|
+
# the amount, or should it come after?
|
152
|
+
|
153
|
+
attr_reader :id, :priority, :iso_code, :iso_numeric, :name, :symbol,
|
154
|
+
:html_entity, :subunit, :subunit_to_unit, :decimal_mark,
|
155
|
+
:thousands_separator, :symbol_first
|
156
|
+
|
157
|
+
alias_method :separator, :decimal_mark
|
158
|
+
alias_method :delimiter, :thousands_separator
|
196
159
|
|
197
160
|
# Create a new +Currency+ object.
|
198
161
|
#
|
@@ -205,12 +168,15 @@ class Money
|
|
205
168
|
# Money::Currency.new(:usd) #=> #<Money::Currency id: usd ...>
|
206
169
|
def initialize(id)
|
207
170
|
id = id.to_s.downcase
|
208
|
-
raise(UnknownCurrency, "Unknown currency `#{id}'") unless self.class.stringified_keys.include?(id)
|
209
171
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
172
|
+
if self.class.stringified_keys.include?(id)
|
173
|
+
@id = id.to_sym
|
174
|
+
data = self.class.table[@id]
|
175
|
+
data.each_pair do |key, value|
|
176
|
+
instance_variable_set(:"@#{key}", value)
|
177
|
+
end
|
178
|
+
else
|
179
|
+
raise UnknownCurrency, "Unknown currency '#{id}'"
|
214
180
|
end
|
215
181
|
end
|
216
182
|
|
@@ -244,9 +210,18 @@ class Money
|
|
244
210
|
# c1 == c1 #=> true
|
245
211
|
# c1 == c2 #=> false
|
246
212
|
def ==(other_currency)
|
247
|
-
self.equal?(other_currency) ||
|
248
|
-
|
213
|
+
self.equal?(other_currency) || compare_ids(other_currency)
|
214
|
+
end
|
215
|
+
|
216
|
+
def compare_ids(other_currency)
|
217
|
+
other_currency_id = if other_currency.is_a?(Currency)
|
218
|
+
other_currency.id.to_s.downcase
|
219
|
+
else
|
220
|
+
other_currency.to_s.downcase
|
221
|
+
end
|
222
|
+
self.id.to_s.downcase == other_currency_id
|
249
223
|
end
|
224
|
+
private :compare_ids
|
250
225
|
|
251
226
|
# Compares +self+ with +other_currency+ and returns +true+ if the are the
|
252
227
|
# same or if their +id+ attributes match.
|
@@ -288,7 +263,7 @@ class Money
|
|
288
263
|
# Returns a string representation corresponding to the upcase +id+
|
289
264
|
# attribute.
|
290
265
|
#
|
291
|
-
#
|
266
|
+
# --
|
292
267
|
# DEV: id.to_s.upcase corresponds to iso_code but don't use ISO_CODE for consistency.
|
293
268
|
#
|
294
269
|
# @return [String]
|
@@ -311,7 +286,7 @@ class Money
|
|
311
286
|
def to_str
|
312
287
|
id.to_s.upcase
|
313
288
|
end
|
314
|
-
|
289
|
+
|
315
290
|
# Returns a symbol representation corresponding to the upcase +id+
|
316
291
|
# attribute.
|
317
292
|
#
|
@@ -321,12 +296,7 @@ class Money
|
|
321
296
|
# Money::Currency.new(:usd).to_sym #=> :USD
|
322
297
|
# Money::Currency.new(:eur).to_sym #=> :EUR
|
323
298
|
def to_sym
|
324
|
-
|
325
|
-
id.upcase
|
326
|
-
else
|
327
|
-
# Ruby <= 1.8.7 doesn't support Symbol#upcase
|
328
|
-
id.to_s.upcase.to_sym
|
329
|
-
end
|
299
|
+
id.to_s.upcase.to_sym
|
330
300
|
end
|
331
301
|
|
332
302
|
# Conversation to +self+.
|
@@ -336,7 +306,6 @@ class Money
|
|
336
306
|
self
|
337
307
|
end
|
338
308
|
|
339
|
-
|
340
309
|
# Returns currency symbol or iso code for currencies with no symbol.
|
341
310
|
#
|
342
311
|
# @return [String]
|
@@ -357,31 +326,24 @@ class Money
|
|
357
326
|
|
358
327
|
# Cache decimal places for subunit_to_unit values. Common ones pre-cached.
|
359
328
|
def self.decimal_places_cache
|
360
|
-
@decimal_places_cache ||= {
|
361
|
-
1 => 0,
|
362
|
-
10 => 1,
|
363
|
-
100 => 2,
|
364
|
-
1000 => 3
|
365
|
-
}
|
329
|
+
@decimal_places_cache ||= {1 => 0, 10 => 1, 100 => 2, 1000 => 3}
|
366
330
|
end
|
367
331
|
|
368
332
|
# The number of decimal places needed.
|
369
333
|
#
|
370
334
|
# @return [Integer]
|
371
335
|
def decimal_places
|
372
|
-
cache
|
373
|
-
places = cache[subunit_to_unit]
|
374
|
-
unless places
|
375
|
-
places = calculate_decimal_places(subunit_to_unit)
|
376
|
-
cache[subunit_to_unit] = places
|
377
|
-
end
|
378
|
-
places
|
336
|
+
cache[subunit_to_unit] ||= calculate_decimal_places(subunit_to_unit)
|
379
337
|
end
|
380
338
|
|
339
|
+
def cache
|
340
|
+
self.class.decimal_places_cache
|
341
|
+
end
|
342
|
+
private :cache
|
343
|
+
|
381
344
|
# If we need to figure out how many decimal places we need we
|
382
345
|
# use repeated integer division.
|
383
346
|
def calculate_decimal_places(num)
|
384
|
-
return 0 if num == 1
|
385
347
|
i = 1
|
386
348
|
while num >= 10
|
387
349
|
num /= 10
|
@@ -390,6 +352,5 @@ class Money
|
|
390
352
|
i
|
391
353
|
end
|
392
354
|
private :calculate_decimal_places
|
393
|
-
|
394
355
|
end
|
395
356
|
end
|