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.
@@ -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