money2 7.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +23 -0
- data/.rspec +1 -0
- data/.travis.yml +24 -0
- data/AUTHORS +126 -0
- data/CHANGELOG.md +619 -0
- data/CONTRIBUTING.md +17 -0
- data/Gemfile +16 -0
- data/LICENSE +23 -0
- data/README.md +438 -0
- data/Rakefile +17 -0
- data/config/currency_backwards_compatible.json +107 -0
- data/config/currency_iso.json +2449 -0
- data/config/currency_non_iso.json +66 -0
- data/lib/money.rb +390 -0
- data/lib/money/allocate.rb +86 -0
- data/lib/money/arithmetic.rb +233 -0
- data/lib/money/bank/base.rb +137 -0
- data/lib/money/bank/single_currency.rb +25 -0
- data/lib/money/bank/variable_exchange.rb +252 -0
- data/lib/money/class_attribute.rb +26 -0
- data/lib/money/currency.rb +402 -0
- data/lib/money/currency/heuristics.rb +150 -0
- data/lib/money/currency/loader.rb +29 -0
- data/lib/money/currency_methods.rb +139 -0
- data/lib/money/formatter.rb +404 -0
- data/lib/money/formatter/to_string.rb +9 -0
- data/lib/money/rates_store/memory.rb +120 -0
- data/lib/money/unaccent.rb +18 -0
- data/lib/money/v6_compatibility.rb +5 -0
- data/lib/money/v6_compatibility/arithmetic.rb +61 -0
- data/lib/money/v6_compatibility/bank_rounding_block.rb +38 -0
- data/lib/money/v6_compatibility/currency_id.rb +29 -0
- data/lib/money/v6_compatibility/format.rb +53 -0
- data/lib/money/v6_compatibility/fractional.rb +74 -0
- data/lib/money/version.rb +3 -0
- data/lib/money2.rb +1 -0
- data/money.gemspec +31 -0
- metadata +207 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'money/bank/base'
|
2
|
+
|
3
|
+
class Money
|
4
|
+
module Bank
|
5
|
+
# Raised when trying to exchange currencies
|
6
|
+
class DifferentCurrencyError < Error; end
|
7
|
+
|
8
|
+
# Class to ensure client code is operating in a single currency
|
9
|
+
# by raising if an exchange attempts to happen.
|
10
|
+
#
|
11
|
+
# This is useful when an application uses multiple currencies but
|
12
|
+
# it usually deals with only one currency at a time so any arithmetic
|
13
|
+
# where exchanges happen are erroneous. Using this as the default bank
|
14
|
+
# means that that these mistakes don't silently do the wrong thing.
|
15
|
+
class SingleCurrency < Base
|
16
|
+
|
17
|
+
# Raises a DifferentCurrencyError to remove possibility of accidentally
|
18
|
+
# exchanging currencies
|
19
|
+
def exchange_with(from, to_currency, &block)
|
20
|
+
raise DifferentCurrencyError,
|
21
|
+
"No exchanging of currencies allowed: #{from.currency} to #{to_currency}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
require 'money/bank/base'
|
2
|
+
require 'money/rates_store/memory'
|
3
|
+
require 'json'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
class Money
|
7
|
+
module Bank
|
8
|
+
# Thrown when an unknown rate format is requested.
|
9
|
+
class UnknownRateFormat < StandardError; end
|
10
|
+
|
11
|
+
# Class for aiding in exchanging money between different currencies. By
|
12
|
+
# default, the +Money+ class uses an object of this class (accessible
|
13
|
+
# through +Money#bank+) for performing currency exchanges.
|
14
|
+
#
|
15
|
+
# By default, +Money::Bank::VariableExchange+ has no knowledge about
|
16
|
+
# conversion rates. One must manually specify them with +add_rate+, after
|
17
|
+
# which one can perform exchanges with +#exchange_with+.
|
18
|
+
#
|
19
|
+
# Exchange rates are stored in memory using +Money::RatesStore::Memory+ by default.
|
20
|
+
# Pass custom rates stores for other types of storage (file, database, etc)
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# bank = Money::Bank::VariableExchange.new
|
24
|
+
# bank.add_rate("USD", "CAD", 1.24515)
|
25
|
+
# bank.add_rate("CAD", "USD", 0.803115)
|
26
|
+
#
|
27
|
+
# c1 = Money.new(100_00, "USD")
|
28
|
+
# c2 = Money.new(100_00, "CAD")
|
29
|
+
#
|
30
|
+
# # Exchange 100 USD to CAD:
|
31
|
+
# bank.exchange_with(c1, "CAD") #=> #<Money fractional:12451 currency:CAD>
|
32
|
+
#
|
33
|
+
# # Exchange 100 CAD to USD:
|
34
|
+
# bank.exchange_with(c2, "USD") #=> #<Money fractional:8031 currency:USD>
|
35
|
+
#
|
36
|
+
# # With custom exchange rates storage
|
37
|
+
# redis_store = MyCustomRedisStore.new(host: 'localhost:6379')
|
38
|
+
# bank = Money::Bank::VariableExchange.new(redis_store)
|
39
|
+
# # Store rates in redis
|
40
|
+
# bank.add_rate 'USD', 'CAD', 0.98
|
41
|
+
# # Get rate from redis
|
42
|
+
# bank.get_rate 'USD', 'CAD'
|
43
|
+
class VariableExchange < Base
|
44
|
+
|
45
|
+
attr_reader :mutex, :store
|
46
|
+
|
47
|
+
# Available formats for importing/exporting rates.
|
48
|
+
RATE_FORMATS = [:json, :ruby, :yaml].freeze
|
49
|
+
SERIALIZER_SEPARATOR = '_TO_'.freeze
|
50
|
+
FORMAT_SERIALIZERS = {:json => JSON, :ruby => Marshal, :yaml => YAML}.freeze
|
51
|
+
|
52
|
+
# Initializes a new +Money::Bank::VariableExchange+ object.
|
53
|
+
# It defaults to using an in-memory, thread safe store instance for
|
54
|
+
# storing exchange rates.
|
55
|
+
#
|
56
|
+
# @param [RateStore] st An exchange rate store, used to persist exchange rate pairs.
|
57
|
+
# @yield [n] Optional block to use when rounding after exchanging one
|
58
|
+
# currency for another. See +Money::bank::base+
|
59
|
+
def initialize(st = Money::RatesStore::Memory.new, &block)
|
60
|
+
@store = st
|
61
|
+
super(&block)
|
62
|
+
end
|
63
|
+
|
64
|
+
def marshal_dump
|
65
|
+
[store.marshal_dump, @rounding_method]
|
66
|
+
end
|
67
|
+
|
68
|
+
def marshal_load(arr)
|
69
|
+
store_info = arr[0]
|
70
|
+
@store = store_info.shift.new(*store_info)
|
71
|
+
@rounding_method = arr[1]
|
72
|
+
end
|
73
|
+
|
74
|
+
# Exchanges the given +Money+ object to a new +Money+ object in
|
75
|
+
# +to_currency+.
|
76
|
+
#
|
77
|
+
# @param [Money] from
|
78
|
+
# The +Money+ object to exchange.
|
79
|
+
# @param [Currency, String, Symbol] to_currency
|
80
|
+
# The currency to exchange to.
|
81
|
+
#
|
82
|
+
# @yield [n] Optional block to use when rounding after exchanging one
|
83
|
+
# currency for another.
|
84
|
+
# @yieldparam [Float] n The resulting float after exchanging one currency
|
85
|
+
# for another.
|
86
|
+
# @yieldreturn [Integer]
|
87
|
+
#
|
88
|
+
# @return [Money]
|
89
|
+
#
|
90
|
+
# @raise +Money::Bank::UnknownRate+ if the conversion rate is unknown.
|
91
|
+
#
|
92
|
+
# @example
|
93
|
+
# bank = Money::Bank::VariableExchange.new
|
94
|
+
# bank.add_rate("USD", "CAD", 1.24515)
|
95
|
+
# bank.add_rate("CAD", "USD", 0.803115)
|
96
|
+
#
|
97
|
+
# c1 = Money.new(100_00, "USD")
|
98
|
+
# c2 = Money.new(100_00, "CAD")
|
99
|
+
#
|
100
|
+
# # Exchange 100 USD to CAD:
|
101
|
+
# bank.exchange_with(c1, "CAD") #=> #<Money fractional:12451 currency:CAD>
|
102
|
+
#
|
103
|
+
# # Exchange 100 CAD to USD:
|
104
|
+
# bank.exchange_with(c2, "USD") #=> #<Money fractional:8031 currency:USD>
|
105
|
+
def exchange_with(from, to_currency, round_mode = nil, &block)
|
106
|
+
to_currency = Currency.wrap(to_currency)
|
107
|
+
return from if from.currency == to_currency
|
108
|
+
rate = get_rate(from.currency, to_currency)
|
109
|
+
unless rate
|
110
|
+
raise UnknownRate, "No conversion rate known for " \
|
111
|
+
"'#{from.currency}' -> '#{to_currency}'"
|
112
|
+
end
|
113
|
+
rate = BigDecimal.new(rate.to_s) unless rate.is_a?(BigDecimal)
|
114
|
+
new_amount = round(from.to_d * rate, to_currency, round_mode, &block)
|
115
|
+
from.send(:build_new, new_amount, to_currency)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Registers a conversion rate and returns it (uses +#set_rate+).
|
119
|
+
# Delegates to +Money::RatesStore::Memory+
|
120
|
+
#
|
121
|
+
# @param [Currency, String, Symbol] from Currency to exchange from.
|
122
|
+
# @param [Currency, String, Symbol] to Currency to exchange to.
|
123
|
+
# @param [Numeric] rate Rate to use when exchanging currencies.
|
124
|
+
#
|
125
|
+
# @return [Numeric]
|
126
|
+
#
|
127
|
+
# @example
|
128
|
+
# bank = Money::Bank::VariableExchange.new
|
129
|
+
# bank.add_rate("USD", "CAD", 1.24515)
|
130
|
+
# bank.add_rate("CAD", "USD", 0.803115)
|
131
|
+
def add_rate(from, to, rate)
|
132
|
+
set_rate(from, to, rate)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Set the rate for the given currencies.
|
136
|
+
# access.
|
137
|
+
# Delegates to +Money::RatesStore::Memory+
|
138
|
+
#
|
139
|
+
# @param [Currency, String, Symbol] from Currency to exchange from.
|
140
|
+
# @param [Currency, String, Symbol] to Currency to exchange to.
|
141
|
+
# @param [Numeric] rate Rate to use when exchanging currencies.
|
142
|
+
# @param [Hash] opts Options hash to set special parameters. Backwards compatibility only.
|
143
|
+
#
|
144
|
+
# @return [Numeric]
|
145
|
+
#
|
146
|
+
# @example
|
147
|
+
# bank = Money::Bank::VariableExchange.new
|
148
|
+
# bank.set_rate("USD", "CAD", 1.24515)
|
149
|
+
# bank.set_rate("CAD", "USD", 0.803115)
|
150
|
+
def set_rate(from, to, rate, opts = {})
|
151
|
+
store.add_rate(Currency.wrap(from).code, Currency.wrap(to).code, rate)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Retrieve the rate for the given currencies.
|
155
|
+
# data access.
|
156
|
+
# Delegates to +Money::RatesStore::Memory+
|
157
|
+
#
|
158
|
+
# @param [Currency, String, Symbol] from Currency to exchange from.
|
159
|
+
# @param [Currency, String, Symbol] to Currency to exchange to.
|
160
|
+
# @param [Hash] opts Options hash to set special parameters. Backwards compatibility only.
|
161
|
+
#
|
162
|
+
# @return [Numeric]
|
163
|
+
#
|
164
|
+
# @example
|
165
|
+
# bank = Money::Bank::VariableExchange.new
|
166
|
+
# bank.set_rate("USD", "CAD", 1.24515)
|
167
|
+
# bank.set_rate("CAD", "USD", 0.803115)
|
168
|
+
#
|
169
|
+
# bank.get_rate("USD", "CAD") #=> 1.24515
|
170
|
+
# bank.get_rate("CAD", "USD") #=> 0.803115
|
171
|
+
def get_rate(from, to, opts = {})
|
172
|
+
store.get_rate(Currency.wrap(from).code, Currency.wrap(to).code)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Return the known rates as a string in the format specified. If +file+
|
176
|
+
# is given will also write the string out to the file specified.
|
177
|
+
# Available formats are +:json+, +:ruby+ and +:yaml+.
|
178
|
+
#
|
179
|
+
# @param [Symbol] format Request format for the resulting string.
|
180
|
+
# @param [String] file Optional file location to write the rates to.
|
181
|
+
# @param [Hash] opts Options hash to set special parameters. Backwards compatibility only.
|
182
|
+
#
|
183
|
+
# @return [String]
|
184
|
+
#
|
185
|
+
# @raise +Money::Bank::UnknownRateFormat+ if format is unknown.
|
186
|
+
#
|
187
|
+
# @example
|
188
|
+
# bank = Money::Bank::VariableExchange.new
|
189
|
+
# bank.set_rate("USD", "CAD", 1.24515)
|
190
|
+
# bank.set_rate("CAD", "USD", 0.803115)
|
191
|
+
#
|
192
|
+
# s = bank.export_rates(:json)
|
193
|
+
# s #=> "{\"USD_TO_CAD\":1.24515,\"CAD_TO_USD\":0.803115}"
|
194
|
+
def export_rates(format, file = nil, opts = {})
|
195
|
+
raise Money::Bank::UnknownRateFormat unless
|
196
|
+
RATE_FORMATS.include? format
|
197
|
+
|
198
|
+
store.transaction do
|
199
|
+
s = FORMAT_SERIALIZERS[format].dump(rates)
|
200
|
+
|
201
|
+
unless file.nil?
|
202
|
+
File.open(file, "w") {|f| f.write(s) }
|
203
|
+
end
|
204
|
+
|
205
|
+
s
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# This should be deprecated.
|
210
|
+
def rates
|
211
|
+
store.each_rate.each_with_object({}) do |(from,to,rate),hash|
|
212
|
+
hash[[from, to].join(SERIALIZER_SEPARATOR)] = rate
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Loads rates provided in +s+ given the specified format. Available
|
217
|
+
# formats are +:json+, +:ruby+ and +:yaml+.
|
218
|
+
# Delegates to +Money::RatesStore::Memory+
|
219
|
+
#
|
220
|
+
# @param [Symbol] format The format of +s+.
|
221
|
+
# @param [String] s The rates string.
|
222
|
+
# @param [Hash] opts Options hash to set special parameters. Backwards compatibility only.
|
223
|
+
#
|
224
|
+
# @return [self]
|
225
|
+
#
|
226
|
+
# @raise +Money::Bank::UnknownRateFormat+ if format is unknown.
|
227
|
+
#
|
228
|
+
# @example
|
229
|
+
# s = "{\"USD_TO_CAD\":1.24515,\"CAD_TO_USD\":0.803115}"
|
230
|
+
# bank = Money::Bank::VariableExchange.new
|
231
|
+
# bank.import_rates(:json, s)
|
232
|
+
#
|
233
|
+
# bank.get_rate("USD", "CAD") #=> 1.24515
|
234
|
+
# bank.get_rate("CAD", "USD") #=> 0.803115
|
235
|
+
def import_rates(format, s, opts = {})
|
236
|
+
raise Money::Bank::UnknownRateFormat unless
|
237
|
+
RATE_FORMATS.include? format
|
238
|
+
|
239
|
+
store.transaction do
|
240
|
+
data = FORMAT_SERIALIZERS[format].load(s)
|
241
|
+
|
242
|
+
data.each do |key, rate|
|
243
|
+
from, to = key.split(SERIALIZER_SEPARATOR)
|
244
|
+
store.add_rate from, to, rate
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
self
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Money
|
2
|
+
module ClassAttribute
|
3
|
+
# Simple version of ActiveSupport's class_attribute. Defines only class-methods,
|
4
|
+
# does not support any options.
|
5
|
+
#
|
6
|
+
# :api: private
|
7
|
+
def class_attribute(*attrs)
|
8
|
+
attrs.each do |name|
|
9
|
+
# singleton_class.class_eval do
|
10
|
+
# define_method(name) { nil }
|
11
|
+
# define_method("#{name}=") do |val|
|
12
|
+
# remove_method(name)
|
13
|
+
# define_method(name) { val }
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
define_singleton_method(name) { nil }
|
17
|
+
define_singleton_method("#{name}=") do |val|
|
18
|
+
singleton_class.class_eval do
|
19
|
+
remove_method(name) if instance_methods(false).include?(name)
|
20
|
+
define_method(name) { val }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,402 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "money/currency/loader"
|
5
|
+
|
6
|
+
class Money
|
7
|
+
|
8
|
+
# Represents a specific currency unit.
|
9
|
+
#
|
10
|
+
# @see http://en.wikipedia.org/wiki/Currency
|
11
|
+
# @see http://iso4217.net/
|
12
|
+
class Currency
|
13
|
+
include Comparable
|
14
|
+
extend Enumerable
|
15
|
+
|
16
|
+
autoload :Heuristics, 'money/currency/heuristics'
|
17
|
+
|
18
|
+
# Keeping cached instances in sync between threads
|
19
|
+
@@monitor = Monitor.new
|
20
|
+
|
21
|
+
# Thrown when an unknown currency is requested.
|
22
|
+
class UnknownCurrency < ArgumentError; end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def new(code)
|
26
|
+
code = prepare_code(code)
|
27
|
+
instances[code] || synchronize { instances[code] ||= super }
|
28
|
+
end
|
29
|
+
|
30
|
+
def instances
|
31
|
+
@instances ||= {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def synchronize(&block)
|
35
|
+
@@monitor.synchronize(&block)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Lookup a currency with given +code+ an returns a +Currency+ instance on
|
39
|
+
# success, +nil+ otherwise.
|
40
|
+
#
|
41
|
+
# @param [String, Symbol, #to_s] code Used to look into +table+ and
|
42
|
+
# retrieve the applicable attributes.
|
43
|
+
#
|
44
|
+
# @return [Money::Currency]
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# Money::Currency.find(:eur) #=> #<Money::Currency id: eur ...>
|
48
|
+
# Money::Currency.find(:foo) #=> nil
|
49
|
+
def find(code)
|
50
|
+
new(code)
|
51
|
+
rescue UnknownCurrency
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
# Bypasses call to Heuristics module, so it can be lazily loaded.
|
56
|
+
def analyze(str)
|
57
|
+
Heuristics.analyze(str, self)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Lookup a currency with given +num+ as an ISO 4217 numeric and returns an
|
61
|
+
# +Currency+ instance on success, +nil+ otherwise.
|
62
|
+
#
|
63
|
+
# @param [#to_s] num used to look into +table+ in +iso_numeric+ and find
|
64
|
+
# the right currency code.
|
65
|
+
#
|
66
|
+
# @return [Money::Currency]
|
67
|
+
#
|
68
|
+
# @example
|
69
|
+
# Money::Currency.find_by_iso_numeric(978) #=> #<Money::Currency id: eur ...>
|
70
|
+
# Money::Currency.find_by_iso_numeric('001') #=> nil
|
71
|
+
def find_by_iso_numeric(num)
|
72
|
+
num = num.to_i
|
73
|
+
code, _ = table.find { |_, currency| currency[:iso_numeric] == num }
|
74
|
+
new(code) if code
|
75
|
+
end
|
76
|
+
|
77
|
+
# Wraps the object in a +Currency+ unless it's already a +Currency+
|
78
|
+
# object.
|
79
|
+
#
|
80
|
+
# @param [Object] object The object to attempt and wrap as a +Currency+
|
81
|
+
# object.
|
82
|
+
#
|
83
|
+
# @return [Money::Currency]
|
84
|
+
#
|
85
|
+
# @example
|
86
|
+
# c1 = Money::Currency.new(:usd)
|
87
|
+
# Money::Currency.wrap(nil) #=> nil
|
88
|
+
# Money::Currency.wrap(c1) #=> #<Money::Currency id: usd ...>
|
89
|
+
# Money::Currency.wrap("usd") #=> #<Money::Currency id: usd ...>
|
90
|
+
def wrap(object)
|
91
|
+
if object.is_a?(self)
|
92
|
+
object
|
93
|
+
else
|
94
|
+
object && new(object)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# List of known currencies.
|
99
|
+
#
|
100
|
+
# == monetary unit
|
101
|
+
# The standard unit of value of a currency, as the dollar in the United States or the peso in Mexico.
|
102
|
+
# http://www.answers.com/topic/monetary-unit
|
103
|
+
# == fractional monetary unit, subunit
|
104
|
+
# A monetary unit that is valued at a fraction (usually one hundredth) of the basic monetary unit
|
105
|
+
# http://www.answers.com/topic/fractional-monetary-unit-subunit
|
106
|
+
#
|
107
|
+
# See http://en.wikipedia.org/wiki/List_of_circulating_currencies and
|
108
|
+
# http://search.cpan.org/~tnguyen/Locale-Currency-Format-1.28/Format.pm
|
109
|
+
def table
|
110
|
+
@table ||= synchronize { Loader.load_all }
|
111
|
+
end
|
112
|
+
|
113
|
+
# List the currencies imported and registered
|
114
|
+
# @return [Array]
|
115
|
+
#
|
116
|
+
# @example
|
117
|
+
# Money::Currency.all()
|
118
|
+
# [#<Currency ..USD>, #<Currency ..CAD>, #<Currency ..EUR>]...
|
119
|
+
def all
|
120
|
+
table.keys.map do |code|
|
121
|
+
new(code).tap do |x|
|
122
|
+
unless x.priority
|
123
|
+
raise "Can't call Currency.all - currency '#{code}' is missing priority"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end.sort_by(&:priority)
|
127
|
+
end
|
128
|
+
|
129
|
+
# We need a string-based validator before creating an unbounded number of
|
130
|
+
# symbols.
|
131
|
+
# http://www.randomhacks.net/articles/2007/01/20/13-ways-of-looking-at-a-ruby-symbol#11
|
132
|
+
# https://github.com/RubyMoney/money/issues/132
|
133
|
+
#
|
134
|
+
# @return [Set]
|
135
|
+
def codes
|
136
|
+
@codes ||= Set.new(table.keys)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Register a new currency
|
140
|
+
#
|
141
|
+
# @param data [Hash] information about the currency
|
142
|
+
# @option priority [Numeric] a numerical value you can use to sort/group
|
143
|
+
# the currency list
|
144
|
+
# @option code [String] the international 3-letter code as defined
|
145
|
+
# by the ISO 4217 standard
|
146
|
+
# @option iso_numeric [Integer] the international 3-digit code as
|
147
|
+
# defined by the ISO 4217 standard
|
148
|
+
# @option name [String] the currency name
|
149
|
+
# @option symbol [String] the currency symbol (UTF-8 encoded)
|
150
|
+
# @option subunit [String] the name of the fractional monetary unit
|
151
|
+
# @option subunit_to_unit [Numeric] the proportion between the unit and
|
152
|
+
# the subunit
|
153
|
+
# @option separator [String] character between the whole and fraction
|
154
|
+
# amounts
|
155
|
+
# @option delimiter [String] character between each thousands place
|
156
|
+
def register(data)
|
157
|
+
code = prepare_code(data.fetch(:code))
|
158
|
+
synchronize do
|
159
|
+
instances.delete(code)
|
160
|
+
table[code] = data
|
161
|
+
end
|
162
|
+
@codes = nil
|
163
|
+
end
|
164
|
+
|
165
|
+
# Unregister a currency.
|
166
|
+
#
|
167
|
+
# @param [Object] data A Hash with the key `:code`, or the ISO code
|
168
|
+
# as a String or Symbol.
|
169
|
+
#
|
170
|
+
# @return [Boolean] true if the currency previously existed, false
|
171
|
+
# if it didn't.
|
172
|
+
def unregister(data)
|
173
|
+
data = data.fetch(:code) if data.is_a?(Hash)
|
174
|
+
code = prepare_code(data)
|
175
|
+
existed = synchronize do
|
176
|
+
instances.delete(code)
|
177
|
+
table.delete(code)
|
178
|
+
end
|
179
|
+
@codes = nil if existed
|
180
|
+
!!existed
|
181
|
+
end
|
182
|
+
|
183
|
+
# Reset all preloaded data, so it will be loaded from config files again.
|
184
|
+
def reset
|
185
|
+
synchronize do
|
186
|
+
@table = nil
|
187
|
+
@instances = nil
|
188
|
+
@codes = nil
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Keeps only given currencies, and clears any data about others.
|
193
|
+
def keep_only(*codes)
|
194
|
+
to_remove = table.keys - codes.map { |x| prepare_code(x) }
|
195
|
+
synchronize do
|
196
|
+
@codes = nil
|
197
|
+
to_remove.each do |code|
|
198
|
+
instances.delete(code)
|
199
|
+
table.delete(code)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def each(&block)
|
205
|
+
all.each(&block)
|
206
|
+
end
|
207
|
+
|
208
|
+
# Cache decimal places for subunit_to_unit values. Common ones pre-cached.
|
209
|
+
def decimal_places_cache
|
210
|
+
@decimal_places_cache ||= Hash.new { |h, k| h[k] = Math.log10(k).ceil }
|
211
|
+
end
|
212
|
+
|
213
|
+
def prepare_code(code)
|
214
|
+
code.to_s.upcase
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# @!attribute [r] id
|
219
|
+
# @return [Symbol] The symbol used to identify the currency, usually THE
|
220
|
+
# lowercase +code+ attribute.
|
221
|
+
# @!attribute [r] priority
|
222
|
+
# @return [Integer] A numerical value you can use to sort/group the
|
223
|
+
# currency list.
|
224
|
+
# @!attribute [r] code
|
225
|
+
# @return [String] The international 3-letter code as defined by the ISO
|
226
|
+
# 4217 standard.
|
227
|
+
# @!attribute [r] iso_numeric
|
228
|
+
# @return [String] The international 3-numeric code as defined by the ISO
|
229
|
+
# 4217 standard.
|
230
|
+
# @!attribute [r] name
|
231
|
+
# @return [String] The currency name.
|
232
|
+
# @!attribute [r] symbol
|
233
|
+
# @return [String] The currency symbol (UTF-8 encoded).
|
234
|
+
# @!attribute [r] disambiguate_symbol
|
235
|
+
# @return [String] Alternative currency used if symbol is ambiguous
|
236
|
+
# @!attribute [r] html_entity
|
237
|
+
# @return [String] The html entity for the currency symbol
|
238
|
+
# @!attribute [r] subunit
|
239
|
+
# @return [String] The name of the fractional monetary unit.
|
240
|
+
# @!attribute [r] subunit_to_unit
|
241
|
+
# @return [Integer] The proportion between the unit and the subunit
|
242
|
+
# @!attribute [r] decimal_mark
|
243
|
+
# @return [String] The decimal mark, or character used to separate the
|
244
|
+
# whole unit from the subunit.
|
245
|
+
# @!attribute [r] thousands_separator
|
246
|
+
# @return [String] The character used to separate thousands grouping of
|
247
|
+
# the whole unit.
|
248
|
+
# @!attribute [r] symbol_first
|
249
|
+
# @return [Boolean] Should the currency symbol precede the amount, or
|
250
|
+
# should it come after?
|
251
|
+
# @!attribute [r] smallest_denomination
|
252
|
+
# @return [Integer] Smallest amount of cash possible (in the subunit of
|
253
|
+
# this currency)
|
254
|
+
|
255
|
+
ATTRS = %w(
|
256
|
+
id
|
257
|
+
alternate_symbols
|
258
|
+
code
|
259
|
+
decimal_mark
|
260
|
+
disambiguate_symbol
|
261
|
+
html_entity
|
262
|
+
iso_numeric
|
263
|
+
name
|
264
|
+
priority
|
265
|
+
smallest_denomination
|
266
|
+
subunit
|
267
|
+
subunit_to_unit
|
268
|
+
symbol
|
269
|
+
symbol_first
|
270
|
+
thousands_separator
|
271
|
+
).map(&:to_sym).freeze
|
272
|
+
|
273
|
+
attr_reader(*ATTRS)
|
274
|
+
|
275
|
+
alias_method :to_sym, :id
|
276
|
+
alias_method :to_s, :code
|
277
|
+
alias_method :to_str, :code
|
278
|
+
alias_method :symbol_first?, :symbol_first
|
279
|
+
alias_method :separator, :decimal_mark
|
280
|
+
alias_method :delimiter, :thousands_separator
|
281
|
+
alias_method :eql?, :==
|
282
|
+
|
283
|
+
# Create a new +Currency+ object.
|
284
|
+
#
|
285
|
+
# @param [String, Symbol, #to_s] id Used to look into +table+ and retrieve
|
286
|
+
# the applicable attributes.
|
287
|
+
#
|
288
|
+
# @return [Money::Currency]
|
289
|
+
#
|
290
|
+
# @example
|
291
|
+
# Money::Currency.new(:usd) #=> #<Money::Currency id: usd ...>
|
292
|
+
def initialize(code)
|
293
|
+
data = self.class.table[code]
|
294
|
+
raise UnknownCurrency, "Unknown currency '#{code}'" unless data
|
295
|
+
@id = code.to_sym
|
296
|
+
(ATTRS - [:id]).each do |attr|
|
297
|
+
instance_variable_set("@#{attr}", data[attr])
|
298
|
+
end
|
299
|
+
@alternate_symbols ||= []
|
300
|
+
end
|
301
|
+
|
302
|
+
# Compares +self+ with +other_currency+ against the value of +priority+
|
303
|
+
# attribute.
|
304
|
+
#
|
305
|
+
# @param [Money::Currency] other_currency The currency to compare to.
|
306
|
+
#
|
307
|
+
# @return [-1,0,1] -1 if less than, 0 is equal to, 1 if greater than
|
308
|
+
#
|
309
|
+
# @example
|
310
|
+
# c1 = Money::Currency.new(:usd)
|
311
|
+
# c2 = Money::Currency.new(:jpy)
|
312
|
+
# c1 <=> c2 #=> 1
|
313
|
+
# c2 <=> c1 #=> -1
|
314
|
+
# c1 <=> c1 #=> 0
|
315
|
+
def <=>(other_currency)
|
316
|
+
# <=> returns nil when one of the values is nil
|
317
|
+
comparison = priority <=> other_currency.priority || 0
|
318
|
+
if comparison == 0
|
319
|
+
id <=> other_currency.id
|
320
|
+
else
|
321
|
+
comparison
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
# Compares +self+ with +other_currency+ and returns +true+ if the are the
|
326
|
+
# same or if their +id+ attributes match.
|
327
|
+
#
|
328
|
+
# @param [Money::Currency] other_currency The currency to compare to.
|
329
|
+
#
|
330
|
+
# @return [Boolean]
|
331
|
+
#
|
332
|
+
# @example
|
333
|
+
# c1 = Money::Currency.new(:usd)
|
334
|
+
# c2 = Money::Currency.new(:jpy)
|
335
|
+
# c1 == c1 #=> true
|
336
|
+
# c1 == c2 #=> false
|
337
|
+
def ==(other_currency)
|
338
|
+
if other_currency.is_a?(Currency)
|
339
|
+
id == other_currency.id
|
340
|
+
else
|
341
|
+
code == other_currency.to_s.upcase
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# Returns a Fixnum hash value based on the +id+ attribute in order to use
|
346
|
+
# functions like & (intersection), group_by, etc.
|
347
|
+
#
|
348
|
+
# @return [Fixnum]
|
349
|
+
#
|
350
|
+
# @example
|
351
|
+
# Money::Currency.new(:usd).hash #=> 428936
|
352
|
+
def hash
|
353
|
+
id.hash
|
354
|
+
end
|
355
|
+
|
356
|
+
# Returns a human readable representation.
|
357
|
+
#
|
358
|
+
# @return [String]
|
359
|
+
#
|
360
|
+
# @example
|
361
|
+
# Money::Currency.new(:usd) #=> #<Currency id: usd ...>
|
362
|
+
def inspect
|
363
|
+
vals = ATTRS.map { |field| "#{field}: #{public_send(field).inspect}" }
|
364
|
+
"#<#{self.class.name} #{vals.join(', ')}>"
|
365
|
+
end
|
366
|
+
|
367
|
+
# Conversion to +self+.
|
368
|
+
#
|
369
|
+
# @return [self]
|
370
|
+
def to_currency
|
371
|
+
self
|
372
|
+
end
|
373
|
+
|
374
|
+
# Returns currency symbol or code for currencies with no symbol.
|
375
|
+
#
|
376
|
+
# @return [String]
|
377
|
+
def symbol_or_code
|
378
|
+
symbol || code
|
379
|
+
end
|
380
|
+
|
381
|
+
def iso?
|
382
|
+
!!iso_numeric
|
383
|
+
end
|
384
|
+
|
385
|
+
# Returns the relation between subunit and unit as a base 10 exponent.
|
386
|
+
#
|
387
|
+
# Note that MGA and MRO are exceptions and are rounded to 1
|
388
|
+
# @see https://en.wikipedia.org/wiki/ISO_4217#Active_codes
|
389
|
+
#
|
390
|
+
# @return [Fixnum]
|
391
|
+
def exponent
|
392
|
+
Math.log10(subunit_to_unit).round
|
393
|
+
end
|
394
|
+
|
395
|
+
# The number of decimal places needed.
|
396
|
+
#
|
397
|
+
# @return [Integer]
|
398
|
+
def decimal_places
|
399
|
+
self.class.decimal_places_cache[subunit_to_unit]
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|