money2 7.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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