money 3.7.1 → 4.0.0

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.
Files changed (43) hide show
  1. data/CHANGELOG.md +384 -351
  2. data/LICENSE +21 -21
  3. data/README.md +243 -214
  4. data/Rakefile +49 -49
  5. data/lib/money.rb +28 -27
  6. data/lib/money/bank/base.rb +131 -131
  7. data/lib/money/bank/variable_exchange.rb +252 -252
  8. data/lib/money/core_extensions.rb +82 -82
  9. data/lib/money/currency.rb +263 -422
  10. data/lib/money/currency_loader.rb +19 -0
  11. data/lib/money/money.rb +405 -405
  12. data/lib/money/money/arithmetic.rb +246 -246
  13. data/lib/money/money/formatting.rb +260 -244
  14. data/lib/money/money/parsing.rb +350 -350
  15. data/money.gemspec +29 -35
  16. data/spec/bank/base_spec.rb +72 -72
  17. data/spec/bank/variable_exchange_spec.rb +238 -238
  18. data/spec/core_extensions_spec.rb +158 -158
  19. data/spec/currency_spec.rb +120 -133
  20. data/spec/money/arithmetic_spec.rb +479 -479
  21. data/spec/money/formatting_spec.rb +383 -375
  22. data/spec/money/parsing_spec.rb +197 -197
  23. data/spec/money_spec.rb +292 -292
  24. data/spec/spec_helper.rb +28 -28
  25. metadata +54 -126
  26. data/lib/money.rbc +0 -184
  27. data/lib/money/bank/base.rbc +0 -818
  28. data/lib/money/bank/variable_exchange.rbc +0 -2550
  29. data/lib/money/core_extensions.rbc +0 -664
  30. data/lib/money/currency.rbc +0 -22708
  31. data/lib/money/money.rbc +0 -3861
  32. data/lib/money/money/arithmetic.rbc +0 -2778
  33. data/lib/money/money/formatting.rbc +0 -2265
  34. data/lib/money/money/parsing.rbc +0 -2737
  35. data/spec/bank/base_spec.rbc +0 -2461
  36. data/spec/bank/variable_exchange_spec.rbc +0 -7541
  37. data/spec/core_extensions_spec.rbc +0 -5921
  38. data/spec/currency_spec.rbc +0 -4535
  39. data/spec/money/arithmetic_spec.rbc +0 -25140
  40. data/spec/money/formatting_spec.rbc +0 -12545
  41. data/spec/money/parsing_spec.rbc +0 -6511
  42. data/spec/money_spec.rbc +0 -9824
  43. data/spec/spec_helper.rbc +0 -575
@@ -0,0 +1,19 @@
1
+ require 'pathname'
2
+
3
+ module CurrencyLoader
4
+ extend self
5
+
6
+ DATA_PATH = Pathname.new(__FILE__).dirname + "../../config/"
7
+
8
+ # Loads and returns the currencies stored in JSON files in the config directory.
9
+ #
10
+ # @return [Hash]
11
+ def load_currencies
12
+ json = File.read(DATA_PATH + 'currency.json')
13
+ currencies = JSON.parse(json, :symbolize_names => true)
14
+
15
+ # merge the currencies kept for backwards compatibility
16
+ json = File.read(DATA_PATH + 'currency_bc.json')
17
+ currencies.merge!(JSON.parse(json, :symbolize_names => true))
18
+ end
19
+ end
data/lib/money/money.rb CHANGED
@@ -1,405 +1,405 @@
1
- # encoding: utf-8
2
- require 'money/bank/variable_exchange'
3
- require 'money/money/arithmetic'
4
- require 'money/money/parsing'
5
- require 'money/money/formatting'
6
-
7
- # Represents an amount of money in a given currency.
8
- class Money
9
- include Comparable
10
- include Arithmetic
11
- include Formatting
12
- include Parsing
13
-
14
- # The value of the money in cents.
15
- #
16
- # @return [Integer]
17
- attr_reader :cents
18
-
19
- # The currency the money is in.
20
- #
21
- # @return [Currency]
22
- attr_reader :currency
23
-
24
- # The +Money::Bank+ based object used to perform currency exchanges with.
25
- #
26
- # @return [Money::Bank::*]
27
- attr_reader :bank
28
-
29
- # Class Methods
30
- class << self
31
- # Each Money object is associated to a bank object, which is responsible
32
- # for currency exchange. This property allows you to specify the default
33
- # bank object. The default value for this property is an instance if
34
- # +Bank::VariableExchange.+ It allows one to specify custom exchange rates.
35
- #
36
- # @return [Money::Bank::*]
37
- attr_accessor :default_bank
38
-
39
- # The default currency, which is used when +Money.new+ is called without an
40
- # explicit currency argument. The default value is Currency.new("USD"). The
41
- # value must be a valid +Money::Currency+ instance.
42
- #
43
- # @return [Money::Currency]
44
- attr_accessor :default_currency
45
-
46
- # Use this to disable i18n even if it's used by other objects in your app.
47
- #
48
- # @return [true,false]
49
- attr_accessor :use_i18n
50
- end
51
-
52
- # Set the default bank for creating new +Money+ objects.
53
- self.default_bank = Bank::VariableExchange.instance
54
-
55
- # Set the default currency for creating new +Money+ object.
56
- self.default_currency = Currency.new("USD")
57
-
58
- # Default to using i18n
59
- self.use_i18n = true
60
-
61
- # Create a new money object with value 0.
62
- #
63
- # @param [Currency, String, Symbol] currency The currency to use.
64
- #
65
- # @return [Money]
66
- #
67
- # @example
68
- # Money.empty #=> #<Money @cents=0>
69
- def self.empty(currency = default_currency)
70
- Money.new(0, currency)
71
- end
72
-
73
- # Creates a new Money object of the given value, using the Canadian
74
- # dollar currency.
75
- #
76
- # @param [Integer] cents The cents value.
77
- #
78
- # @return [Money]
79
- #
80
- # @example
81
- # n = Money.ca_dollar(100)
82
- # n.cents #=> 100
83
- # n.currency #=> #<Money::Currency id: cad>
84
- def self.ca_dollar(cents)
85
- Money.new(cents, "CAD")
86
- end
87
-
88
- # Creates a new Money object of the given value, using the American dollar
89
- # currency.
90
- #
91
- # @param [Integer] cents The cents value.
92
- #
93
- # @return [Money]
94
- #
95
- # @example
96
- # n = Money.us_dollar(100)
97
- # n.cents #=> 100
98
- # n.currency #=> #<Money::Currency id: usd>
99
- def self.us_dollar(cents)
100
- Money.new(cents, "USD")
101
- end
102
-
103
- # Creates a new Money object of the given value, using the Euro currency.
104
- #
105
- # @param [Integer] cents The cents value.
106
- #
107
- # @return [Money]
108
- #
109
- # @example
110
- # n = Money.euro(100)
111
- # n.cents #=> 100
112
- # n.currency #=> #<Money::Currency id: eur>
113
- def self.euro(cents)
114
- Money.new(cents, "EUR")
115
- end
116
-
117
-
118
- # Creates a new Money object of +amount+ value in dollars,
119
- # with given +currency+.
120
- #
121
- # The amount value is expressed in +dollars+
122
- # where the +dollar+ is the main monetary unit,
123
- # opposite to the subunit-based representation
124
- # used internally by this library called +cents+.
125
- #
126
- # @param [Numeric] amount The money amount, in dollars.
127
- # @param [Currency, String, Symbol] currency The currency format.
128
- # @param [Money::Bank::*] bank The exchange bank to use.
129
- #
130
- # @return [Money]
131
- #
132
- # @example
133
- # Money.new_with_dollars(100)
134
- # #=> #<Money @cents=10000 @currency="USD">
135
- # Money.new_with_dollars(100, "USD")
136
- # #=> #<Money @cents=10000 @currency="USD">
137
- # Money.new_with_dollars(100, "EUR")
138
- # #=> #<Money @cents=10000 @currency="EUR">
139
- #
140
- # @see Money.new
141
- #
142
- def self.new_with_dollars(amount, currency = Money.default_currency, bank = Money.default_bank)
143
- money = from_numeric(amount, currency)
144
- # Hack! You can't change a bank
145
- money.instance_variable_set("@bank", bank)
146
- money
147
- end
148
- # Adds a new exchange rate to the default bank and return the rate.
149
- #
150
- # @param [Currency, String, Symbol] from_currency Currency to exchange from.
151
- # @param [Currency, String, Symbol] to_currency Currency to exchange to.
152
- # @param [Numeric] rate Rate to exchange with.
153
- #
154
- # @return [Numeric]
155
- #
156
- # @example
157
- # Money.add_rate("USD", "CAD", 1.25) #=> 1.25
158
- def self.add_rate(from_currency, to_currency, rate)
159
- Money.default_bank.add_rate(from_currency, to_currency, rate)
160
- end
161
-
162
-
163
- # Creates a new Money object of +cents+ value in cents,
164
- # with given +currency+.
165
- #
166
- # Alternatively you can use the convenience
167
- # methods like {Money.ca_dollar} and {Money.us_dollar}.
168
- #
169
- # @param [Integer] cents The money amount, in cents.
170
- # @param [Currency, String, Symbol] currency The currency format.
171
- # @param [Money::Bank::*] bank The exchange bank to use.
172
- #
173
- # @return [Money]
174
- #
175
- # @example
176
- # Money.new(100)
177
- # #=> #<Money @cents=100 @currency="USD">
178
- # Money.new(100, "USD")
179
- # #=> #<Money @cents=100 @currency="USD">
180
- # Money.new(100, "EUR")
181
- # #=> #<Money @cents=100 @currency="EUR">
182
- #
183
- # @see Money.new_with_dollars
184
- #
185
- def initialize(cents, currency = Money.default_currency, bank = Money.default_bank)
186
- @cents = cents.round.to_i
187
- @currency = Currency.wrap(currency)
188
- @bank = bank
189
- end
190
-
191
- # Returns the value of the money in dollars,
192
- # instead of in cents.
193
- #
194
- # @return [Float]
195
- #
196
- # @example
197
- # Money.new(100).dollars # => 1.0
198
- # Money.new_with_dollars(1).dollar # => 1.0
199
- #
200
- # @see #to_f
201
- # @see #cents
202
- #
203
- def dollars
204
- to_f
205
- end
206
-
207
- # Return string representation of currency object
208
- #
209
- # @return [String]
210
- #
211
- # @example
212
- # Money.new(100, :USD).currency_as_string #=> "USD"
213
- def currency_as_string
214
- self.currency.to_s
215
- end
216
-
217
- # Set currency object using a string
218
- #
219
- # @param [String] val The currency string.
220
- #
221
- # @return [Money::Currency]
222
- #
223
- # @example
224
- # Money.new(100).currency_as_string("CAD") #=> #<Money::Currency id: cad>
225
- def currency_as_string=(val)
226
- @currency = Currency.wrap(val)
227
- end
228
-
229
- # Returns a Fixnum hash value based on the +cents+ and +currency+ attributes
230
- # in order to use functions like & (intersection), group_by, etc.
231
- #
232
- # @return [Fixnum]
233
- #
234
- # @example
235
- # Money.new(100).hash #=> 908351
236
- def hash
237
- [cents.hash, currency.hash].hash
238
- end
239
-
240
- # Uses +Currency#symbol+. If +nil+ is returned, defaults to "¤".
241
- #
242
- # @return [String]
243
- #
244
- # @example
245
- # Money.new(100, "USD").symbol #=> "$"
246
- def symbol
247
- currency.symbol || "¤"
248
- end
249
-
250
- # Returns the amount of money as a string.
251
- #
252
- # @return [String]
253
- #
254
- # @example
255
- # Money.ca_dollar(100).to_s #=> "1.00"
256
- def to_s
257
- unit, subunit = cents.abs.divmod(currency.subunit_to_unit).map{|o| o.to_s}
258
- if currency.decimal_places == 0
259
- return "-#{unit}" if cents < 0
260
- return unit
261
- end
262
- subunit = (("0" * currency.decimal_places) + subunit)[(-1*currency.decimal_places)..-1]
263
- return "-#{unit}#{decimal_mark}#{subunit}" if cents < 0
264
- "#{unit}#{decimal_mark}#{subunit}"
265
- end
266
-
267
- # Return the amount of money as a BigDecimal.
268
- #
269
- # @return [BigDecimal]
270
- #
271
- # @example
272
- # Money.us_dollar(100).to_d => BigDecimal.new("1.0")
273
- def to_d
274
- BigDecimal.new(cents.to_s) / BigDecimal.new(currency.subunit_to_unit.to_s)
275
- end
276
-
277
- # Return the amount of money as a float. Floating points cannot guarantee
278
- # precision. Therefore, this function should only be used when you no longer
279
- # need to represent currency or working with another system that requires
280
- # decimals.
281
- #
282
- # @return [Float]
283
- #
284
- # @example
285
- # Money.us_dollar(100).to_f => 1.0
286
- def to_f
287
- to_d.to_f
288
- end
289
-
290
- # Receive the amount of this money object in another Currency.
291
- #
292
- # @param [Currency, String, Symbol] other_currency Currency to exchange to.
293
- #
294
- # @return [Money]
295
- #
296
- # @example
297
- # Money.new(2000, "USD").exchange_to("EUR")
298
- # Money.new(2000, "USD").exchange_to(Currency.new("EUR"))
299
- def exchange_to(other_currency)
300
- other_currency = Currency.wrap(other_currency)
301
- @bank.exchange_with(self, other_currency)
302
- end
303
-
304
- # Receive a money object with the same amount as the current Money object
305
- # in american dollars.
306
- #
307
- # @return [Money]
308
- #
309
- # @example
310
- # n = Money.new(100, "CAD").as_us_dollar
311
- # n.currency #=> #<Money::Currency id: usd>
312
- def as_us_dollar
313
- exchange_to("USD")
314
- end
315
-
316
- # Receive a money object with the same amount as the current Money object
317
- # in canadian dollar.
318
- #
319
- # @return [Money]
320
- #
321
- # @example
322
- # n = Money.new(100, "USD").as_ca_dollar
323
- # n.currency #=> #<Money::Currency id: cad>
324
- def as_ca_dollar
325
- exchange_to("CAD")
326
- end
327
-
328
- # Receive a money object with the same amount as the current Money object
329
- # in euro.
330
- #
331
- # @return [Money]
332
- #
333
- # @example
334
- # n = Money.new(100, "USD").as_euro
335
- # n.currency #=> #<Money::Currency id: eur>
336
- def as_euro
337
- exchange_to("EUR")
338
- end
339
-
340
- # Conversation to +self+.
341
- #
342
- # @return [self]
343
- def to_money
344
- self
345
- end
346
-
347
- # Common inspect function
348
- #
349
- # @return [String]
350
- def inspect
351
- "#<Money cents:#{cents} currency:#{currency}>"
352
- end
353
-
354
- # Allocates money between different parties without loosing pennies.
355
- # After the mathmatically split has been performed, left over pennies will
356
- # be distributed round-robin amongst the parties. This means that parties
357
- # listed first will likely recieve more pennies then ones that are listed later
358
- #
359
- # @param [0.50, 0.25, 0.25] to give 50% of the cash to party1, 25% ot party2, and 25% to party3.
360
- #
361
- # @return [Array<Money, Money, Money>]
362
- #
363
- # @example
364
- # Money.new(5, "USD").allocate([0.3,0.7)) #=> [Money.new(2), Money.new(3)]
365
- # Money.new(100, "USD").allocate([0.33,0.33,0.33]) #=> [Money.new(34), Money.new(33), Money.new(33)]
366
- def allocate(splits)
367
- allocations = splits.inject(0.0) {|sum, i| sum += i }
368
- raise ArgumentError, "splits add to more then 100%" if (allocations - 1.0) > Float::EPSILON
369
-
370
- left_over = cents
371
-
372
- amounts = splits.collect do |ratio|
373
- fraction = (cents * ratio / allocations).floor
374
- left_over -= fraction
375
- fraction
376
- end
377
-
378
- left_over.times { |i| amounts[i % amounts.length] += 1 }
379
-
380
- return amounts.collect { |cents| Money.new(cents, currency) }
381
- end
382
-
383
- # Split money amongst parties evenly without loosing pennies.
384
- #
385
- # @param [2] number of parties.
386
- #
387
- # @return [Array<Money, Money, Money>]
388
- #
389
- # @example
390
- # Money.new(100, "USD").split(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
391
- def split(num)
392
- raise ArgumentError, "need at least one party" if num < 1
393
- low = Money.new(cents / num)
394
- high = Money.new(low.cents + 1)
395
-
396
- remainder = cents % num
397
- result = []
398
-
399
- num.times do |index|
400
- result[index] = index < remainder ? high : low
401
- end
402
-
403
- return result
404
- end
405
- end
1
+ # encoding: utf-8
2
+ require 'money/bank/variable_exchange'
3
+ require 'money/money/arithmetic'
4
+ require 'money/money/parsing'
5
+ require 'money/money/formatting'
6
+
7
+ # Represents an amount of money in a given currency.
8
+ class Money
9
+ include Comparable
10
+ include Arithmetic
11
+ include Formatting
12
+ include Parsing
13
+
14
+ # The value of the money in cents.
15
+ #
16
+ # @return [Integer]
17
+ attr_reader :cents
18
+
19
+ # The currency the money is in.
20
+ #
21
+ # @return [Currency]
22
+ attr_reader :currency
23
+
24
+ # The +Money::Bank+ based object used to perform currency exchanges with.
25
+ #
26
+ # @return [Money::Bank::*]
27
+ attr_reader :bank
28
+
29
+ # Class Methods
30
+ class << self
31
+ # Each Money object is associated to a bank object, which is responsible
32
+ # for currency exchange. This property allows you to specify the default
33
+ # bank object. The default value for this property is an instance if
34
+ # +Bank::VariableExchange.+ It allows one to specify custom exchange rates.
35
+ #
36
+ # @return [Money::Bank::*]
37
+ attr_accessor :default_bank
38
+
39
+ # The default currency, which is used when +Money.new+ is called without an
40
+ # explicit currency argument. The default value is Currency.new("USD"). The
41
+ # value must be a valid +Money::Currency+ instance.
42
+ #
43
+ # @return [Money::Currency]
44
+ attr_accessor :default_currency
45
+
46
+ # Use this to disable i18n even if it's used by other objects in your app.
47
+ #
48
+ # @return [true,false]
49
+ attr_accessor :use_i18n
50
+ end
51
+
52
+ # Set the default bank for creating new +Money+ objects.
53
+ self.default_bank = Bank::VariableExchange.instance
54
+
55
+ # Set the default currency for creating new +Money+ object.
56
+ self.default_currency = Currency.new("USD")
57
+
58
+ # Default to using i18n
59
+ self.use_i18n = true
60
+
61
+ # Create a new money object with value 0.
62
+ #
63
+ # @param [Currency, String, Symbol] currency The currency to use.
64
+ #
65
+ # @return [Money]
66
+ #
67
+ # @example
68
+ # Money.empty #=> #<Money @cents=0>
69
+ def self.empty(currency = default_currency)
70
+ Money.new(0, currency)
71
+ end
72
+
73
+ # Creates a new Money object of the given value, using the Canadian
74
+ # dollar currency.
75
+ #
76
+ # @param [Integer] cents The cents value.
77
+ #
78
+ # @return [Money]
79
+ #
80
+ # @example
81
+ # n = Money.ca_dollar(100)
82
+ # n.cents #=> 100
83
+ # n.currency #=> #<Money::Currency id: cad>
84
+ def self.ca_dollar(cents)
85
+ Money.new(cents, "CAD")
86
+ end
87
+
88
+ # Creates a new Money object of the given value, using the American dollar
89
+ # currency.
90
+ #
91
+ # @param [Integer] cents The cents value.
92
+ #
93
+ # @return [Money]
94
+ #
95
+ # @example
96
+ # n = Money.us_dollar(100)
97
+ # n.cents #=> 100
98
+ # n.currency #=> #<Money::Currency id: usd>
99
+ def self.us_dollar(cents)
100
+ Money.new(cents, "USD")
101
+ end
102
+
103
+ # Creates a new Money object of the given value, using the Euro currency.
104
+ #
105
+ # @param [Integer] cents The cents value.
106
+ #
107
+ # @return [Money]
108
+ #
109
+ # @example
110
+ # n = Money.euro(100)
111
+ # n.cents #=> 100
112
+ # n.currency #=> #<Money::Currency id: eur>
113
+ def self.euro(cents)
114
+ Money.new(cents, "EUR")
115
+ end
116
+
117
+
118
+ # Creates a new Money object of +amount+ value in dollars,
119
+ # with given +currency+.
120
+ #
121
+ # The amount value is expressed in +dollars+
122
+ # where the +dollar+ is the main monetary unit,
123
+ # opposite to the subunit-based representation
124
+ # used internally by this library called +cents+.
125
+ #
126
+ # @param [Numeric] amount The money amount, in dollars.
127
+ # @param [Currency, String, Symbol] currency The currency format.
128
+ # @param [Money::Bank::*] bank The exchange bank to use.
129
+ #
130
+ # @return [Money]
131
+ #
132
+ # @example
133
+ # Money.new_with_dollars(100)
134
+ # #=> #<Money @cents=10000 @currency="USD">
135
+ # Money.new_with_dollars(100, "USD")
136
+ # #=> #<Money @cents=10000 @currency="USD">
137
+ # Money.new_with_dollars(100, "EUR")
138
+ # #=> #<Money @cents=10000 @currency="EUR">
139
+ #
140
+ # @see Money.new
141
+ #
142
+ def self.new_with_dollars(amount, currency = Money.default_currency, bank = Money.default_bank)
143
+ money = from_numeric(amount, currency)
144
+ # Hack! You can't change a bank
145
+ money.instance_variable_set("@bank", bank)
146
+ money
147
+ end
148
+ # Adds a new exchange rate to the default bank and return the rate.
149
+ #
150
+ # @param [Currency, String, Symbol] from_currency Currency to exchange from.
151
+ # @param [Currency, String, Symbol] to_currency Currency to exchange to.
152
+ # @param [Numeric] rate Rate to exchange with.
153
+ #
154
+ # @return [Numeric]
155
+ #
156
+ # @example
157
+ # Money.add_rate("USD", "CAD", 1.25) #=> 1.25
158
+ def self.add_rate(from_currency, to_currency, rate)
159
+ Money.default_bank.add_rate(from_currency, to_currency, rate)
160
+ end
161
+
162
+
163
+ # Creates a new Money object of +cents+ value in cents,
164
+ # with given +currency+.
165
+ #
166
+ # Alternatively you can use the convenience
167
+ # methods like {Money.ca_dollar} and {Money.us_dollar}.
168
+ #
169
+ # @param [Integer] cents The money amount, in cents.
170
+ # @param [Currency, String, Symbol] currency The currency format.
171
+ # @param [Money::Bank::*] bank The exchange bank to use.
172
+ #
173
+ # @return [Money]
174
+ #
175
+ # @example
176
+ # Money.new(100)
177
+ # #=> #<Money @cents=100 @currency="USD">
178
+ # Money.new(100, "USD")
179
+ # #=> #<Money @cents=100 @currency="USD">
180
+ # Money.new(100, "EUR")
181
+ # #=> #<Money @cents=100 @currency="EUR">
182
+ #
183
+ # @see Money.new_with_dollars
184
+ #
185
+ def initialize(cents, currency = Money.default_currency, bank = Money.default_bank)
186
+ @cents = cents.round.to_i
187
+ @currency = Currency.wrap(currency)
188
+ @bank = bank
189
+ end
190
+
191
+ # Returns the value of the money in dollars,
192
+ # instead of in cents.
193
+ #
194
+ # @return [Float]
195
+ #
196
+ # @example
197
+ # Money.new(100).dollars # => 1.0
198
+ # Money.new_with_dollars(1).dollar # => 1.0
199
+ #
200
+ # @see #to_f
201
+ # @see #cents
202
+ #
203
+ def dollars
204
+ to_f
205
+ end
206
+
207
+ # Return string representation of currency object
208
+ #
209
+ # @return [String]
210
+ #
211
+ # @example
212
+ # Money.new(100, :USD).currency_as_string #=> "USD"
213
+ def currency_as_string
214
+ self.currency.to_s
215
+ end
216
+
217
+ # Set currency object using a string
218
+ #
219
+ # @param [String] val The currency string.
220
+ #
221
+ # @return [Money::Currency]
222
+ #
223
+ # @example
224
+ # Money.new(100).currency_as_string("CAD") #=> #<Money::Currency id: cad>
225
+ def currency_as_string=(val)
226
+ @currency = Currency.wrap(val)
227
+ end
228
+
229
+ # Returns a Fixnum hash value based on the +cents+ and +currency+ attributes
230
+ # in order to use functions like & (intersection), group_by, etc.
231
+ #
232
+ # @return [Fixnum]
233
+ #
234
+ # @example
235
+ # Money.new(100).hash #=> 908351
236
+ def hash
237
+ [cents.hash, currency.hash].hash
238
+ end
239
+
240
+ # Uses +Currency#symbol+. If +nil+ is returned, defaults to "¤".
241
+ #
242
+ # @return [String]
243
+ #
244
+ # @example
245
+ # Money.new(100, "USD").symbol #=> "$"
246
+ def symbol
247
+ currency.symbol || "¤"
248
+ end
249
+
250
+ # Returns the amount of money as a string.
251
+ #
252
+ # @return [String]
253
+ #
254
+ # @example
255
+ # Money.ca_dollar(100).to_s #=> "1.00"
256
+ def to_s
257
+ unit, subunit = cents.abs.divmod(currency.subunit_to_unit).map{|o| o.to_s}
258
+ if currency.decimal_places == 0
259
+ return "-#{unit}" if cents < 0
260
+ return unit
261
+ end
262
+ subunit = (("0" * currency.decimal_places) + subunit)[(-1*currency.decimal_places)..-1]
263
+ return "-#{unit}#{decimal_mark}#{subunit}" if cents < 0
264
+ "#{unit}#{decimal_mark}#{subunit}"
265
+ end
266
+
267
+ # Return the amount of money as a BigDecimal.
268
+ #
269
+ # @return [BigDecimal]
270
+ #
271
+ # @example
272
+ # Money.us_dollar(100).to_d => BigDecimal.new("1.0")
273
+ def to_d
274
+ BigDecimal.new(cents.to_s) / BigDecimal.new(currency.subunit_to_unit.to_s)
275
+ end
276
+
277
+ # Return the amount of money as a float. Floating points cannot guarantee
278
+ # precision. Therefore, this function should only be used when you no longer
279
+ # need to represent currency or working with another system that requires
280
+ # decimals.
281
+ #
282
+ # @return [Float]
283
+ #
284
+ # @example
285
+ # Money.us_dollar(100).to_f => 1.0
286
+ def to_f
287
+ to_d.to_f
288
+ end
289
+
290
+ # Receive the amount of this money object in another Currency.
291
+ #
292
+ # @param [Currency, String, Symbol] other_currency Currency to exchange to.
293
+ #
294
+ # @return [Money]
295
+ #
296
+ # @example
297
+ # Money.new(2000, "USD").exchange_to("EUR")
298
+ # Money.new(2000, "USD").exchange_to(Currency.new("EUR"))
299
+ def exchange_to(other_currency)
300
+ other_currency = Currency.wrap(other_currency)
301
+ @bank.exchange_with(self, other_currency)
302
+ end
303
+
304
+ # Receive a money object with the same amount as the current Money object
305
+ # in american dollars.
306
+ #
307
+ # @return [Money]
308
+ #
309
+ # @example
310
+ # n = Money.new(100, "CAD").as_us_dollar
311
+ # n.currency #=> #<Money::Currency id: usd>
312
+ def as_us_dollar
313
+ exchange_to("USD")
314
+ end
315
+
316
+ # Receive a money object with the same amount as the current Money object
317
+ # in canadian dollar.
318
+ #
319
+ # @return [Money]
320
+ #
321
+ # @example
322
+ # n = Money.new(100, "USD").as_ca_dollar
323
+ # n.currency #=> #<Money::Currency id: cad>
324
+ def as_ca_dollar
325
+ exchange_to("CAD")
326
+ end
327
+
328
+ # Receive a money object with the same amount as the current Money object
329
+ # in euro.
330
+ #
331
+ # @return [Money]
332
+ #
333
+ # @example
334
+ # n = Money.new(100, "USD").as_euro
335
+ # n.currency #=> #<Money::Currency id: eur>
336
+ def as_euro
337
+ exchange_to("EUR")
338
+ end
339
+
340
+ # Conversation to +self+.
341
+ #
342
+ # @return [self]
343
+ def to_money
344
+ self
345
+ end
346
+
347
+ # Common inspect function
348
+ #
349
+ # @return [String]
350
+ def inspect
351
+ "#<Money cents:#{cents} currency:#{currency}>"
352
+ end
353
+
354
+ # Allocates money between different parties without loosing pennies.
355
+ # After the mathmatically split has been performed, left over pennies will
356
+ # be distributed round-robin amongst the parties. This means that parties
357
+ # listed first will likely recieve more pennies then ones that are listed later
358
+ #
359
+ # @param [0.50, 0.25, 0.25] to give 50% of the cash to party1, 25% ot party2, and 25% to party3.
360
+ #
361
+ # @return [Array<Money, Money, Money>]
362
+ #
363
+ # @example
364
+ # Money.new(5, "USD").allocate([0.3,0.7)) #=> [Money.new(2), Money.new(3)]
365
+ # Money.new(100, "USD").allocate([0.33,0.33,0.33]) #=> [Money.new(34), Money.new(33), Money.new(33)]
366
+ def allocate(splits)
367
+ allocations = splits.inject(0.0) {|sum, i| sum += i }
368
+ raise ArgumentError, "splits add to more then 100%" if (allocations - 1.0) > Float::EPSILON
369
+
370
+ left_over = cents
371
+
372
+ amounts = splits.collect do |ratio|
373
+ fraction = (cents * ratio / allocations).floor
374
+ left_over -= fraction
375
+ fraction
376
+ end
377
+
378
+ left_over.times { |i| amounts[i % amounts.length] += 1 }
379
+
380
+ return amounts.collect { |cents| Money.new(cents, currency) }
381
+ end
382
+
383
+ # Split money amongst parties evenly without loosing pennies.
384
+ #
385
+ # @param [2] number of parties.
386
+ #
387
+ # @return [Array<Money, Money, Money>]
388
+ #
389
+ # @example
390
+ # Money.new(100, "USD").split(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
391
+ def split(num)
392
+ raise ArgumentError, "need at least one party" if num < 1
393
+ low = Money.new(cents / num)
394
+ high = Money.new(low.cents + 1)
395
+
396
+ remainder = cents % num
397
+ result = []
398
+
399
+ num.times do |index|
400
+ result[index] = index < remainder ? high : low
401
+ end
402
+
403
+ return result
404
+ end
405
+ end