money2 7.0.0.rc1
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 +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
|