money 3.0.5 → 3.1.0.pre1
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.
- data/CHANGELOG.rdoc +20 -9
- data/README.rdoc +10 -23
- data/Rakefile +5 -5
- data/VERSION +1 -1
- data/lib/money.rb +1 -0
- data/lib/money/bank/base.rb +84 -0
- data/lib/money/bank/variable_exchange.rb +85 -0
- data/lib/money/core_extensions.rb +8 -1
- data/lib/money/currency.rb +16 -1
- data/lib/money/defaults.rb +5 -11
- data/lib/money/deprecations.rb +21 -0
- data/lib/money/money.rb +18 -6
- data/money.gemspec +21 -14
- data/spec/bank/base_spec.rb +57 -0
- data/spec/bank/variable_exchange_spec.rb +230 -0
- data/{test → spec}/core_extensions_spec.rb +6 -3
- data/{test → spec}/currency_spec.rb +16 -5
- data/spec/deprecations_spec.rb +16 -0
- data/{test → spec}/money_spec.rb +19 -7
- data/spec/spec_helper.rb +8 -0
- metadata +30 -19
- data/lib/money/errors.rb +0 -4
- data/lib/money/variable_exchange_bank.rb +0 -82
- data/test/exchange_bank_spec.rb +0 -101
data/CHANGELOG.rdoc
CHANGED
@@ -1,11 +1,23 @@
|
|
1
|
+
== Money 3.1.0.pre1
|
2
|
+
* Implemented Money::Bank::Base (closes #14)
|
3
|
+
* Added Bank::Base#exchange_with
|
4
|
+
* Deprecated Bank::Base#exchange (removal in 3.2.0)
|
5
|
+
* Implented Money::Bank::VariableExchange
|
6
|
+
* Deprecated Money::VariableExchangeBank (removal in 3.2.0)
|
7
|
+
* Set Money::SYMBOLS, Money::SEPARATORS and Money::DELIMITERS
|
8
|
+
deprecation target to Money 3.2.0. (closes #16)
|
9
|
+
* Fixed rounding error in Numeric#to_money (closes #15)
|
10
|
+
* Implemented #has for Money and Money::Currency
|
11
|
+
* Refactored test suite to conform to RSpec conventions
|
12
|
+
* Moved project from github.com/FooBarWidget to github.com/RubyMoney
|
13
|
+
* Added Simone Carletti to list of authors
|
14
|
+
|
1
15
|
== Money 3.0.5
|
2
16
|
* Added Money#abs
|
3
|
-
* Updated Currency#subunit_to_unit documentation (it's an integer not a
|
4
|
-
|
5
|
-
* Fixed issue when exchanging currencies with different :subunit_to_unit
|
6
|
-
values
|
17
|
+
* Updated Currency#subunit_to_unit documentation (it's an integer not a string)
|
18
|
+
* Fixed issue when exchanging currencies with different :subunit_to_unit values
|
7
19
|
* Added ability to pass a block to VariableExchangeBank#new or #exchange,
|
8
|
-
|
20
|
+
specifying a custom truncation method
|
9
21
|
* Added optional currency argument to Numeric#to_money
|
10
22
|
* Added optional currency argument to String#to_money
|
11
23
|
* Numeric#to_money now respects :subunit_to_unit
|
@@ -20,7 +32,7 @@
|
|
20
32
|
|
21
33
|
== Money 3.0.3
|
22
34
|
* Added #currency_as_string and #currency_as_string= for easier integration
|
23
|
-
|
35
|
+
with ActiveRecord/Rails
|
24
36
|
|
25
37
|
== Money 3.0.2
|
26
38
|
* Added #div, #divmod, #modulo, #% and #remainder methods
|
@@ -28,12 +40,11 @@
|
|
28
40
|
== Money 3.0.1
|
29
41
|
* Added #eql? method
|
30
42
|
* Updated Numeric#to_money to work with all children of Numeric
|
31
|
-
|
43
|
+
(BigDecimal, Integer, Fixnum, etc)
|
32
44
|
|
33
45
|
== Money 3.0.0
|
34
46
|
* Version Bump due to compatibility changes with ActiveRecord. See
|
35
|
-
http://github.com/
|
36
|
-
information.
|
47
|
+
http://github.com/RubyMoney/money/issues#issue/4/comment/224880 for more information.
|
37
48
|
|
38
49
|
== Money 2.3.0
|
39
50
|
* Currency is now represented by a Currency Object instead of a string.
|
data/README.rdoc
CHANGED
@@ -4,8 +4,8 @@ This library aids one in handling money and different currencies. Features:
|
|
4
4
|
|
5
5
|
- Provides a <tt>Money</tt> class which encapsulates all information about an certain
|
6
6
|
amount of money, such as its value and its currency.
|
7
|
-
-
|
8
|
-
a
|
7
|
+
- Provides a <tt>Money::Currency</tt> class which encapsulates all information about
|
8
|
+
a monetary unit.
|
9
9
|
- Represents monetary values as integers, in cents. This avoids floating point
|
10
10
|
rounding errors.
|
11
11
|
- Represents currency as <tt>Money::Currency</tt> instances providing an high level of flexibility.
|
@@ -17,7 +17,7 @@ Resources:
|
|
17
17
|
|
18
18
|
- Website: http://money.rubyforge.org
|
19
19
|
- RDoc API: http://money.rubyforge.org
|
20
|
-
- Git repository: http://github.com/
|
20
|
+
- Git repository: http://github.com/RubyMoney/money
|
21
21
|
|
22
22
|
== Attention
|
23
23
|
|
@@ -36,7 +36,7 @@ Install stable releases with the following command:
|
|
36
36
|
|
37
37
|
The development version (hosted on Github) can be installed with:
|
38
38
|
|
39
|
-
git clone git://github.com/
|
39
|
+
git clone git://github.com/RubyMoney/money.git
|
40
40
|
cd money
|
41
41
|
rake install
|
42
42
|
|
@@ -107,7 +107,7 @@ exist to provide a basic API you can take advantage of to build your application
|
|
107
107
|
The priority attribute is an arbitrary numerical value you can assign to the <tt>Money::Currency</tt>
|
108
108
|
and use in sorting/grouping operation.
|
109
109
|
|
110
|
-
For instance, let's assume your Rails application needs to a currency selector
|
110
|
+
For instance, let's assume your Rails application needs to render a currency selector
|
111
111
|
like the one available at http://finance.yahoo.com/currency-converter/
|
112
112
|
You can create a couple of custom methods to return the list of major_currencies
|
113
113
|
and all_currencies as follows:
|
@@ -183,23 +183,10 @@ Use the +compose_of+ helper to let Active Record deal with embedding the money
|
|
183
183
|
object in your models. The following example requires a +cents+ and a +currency+
|
184
184
|
field.
|
185
185
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
:mapping => [%w(cents cents), %w(currency currency_as_string)],
|
191
|
-
:constructor => Proc.new { |cents, currency| Money.new(cents || 0, currency || Money.default_currency) }
|
192
|
-
|
193
|
-
private
|
194
|
-
validate :cents_not_zero
|
195
|
-
|
196
|
-
def cents_not_zero
|
197
|
-
errors.add("cents", "cannot be zero or less") unless cents > 0
|
198
|
-
end
|
199
|
-
|
200
|
-
validates_presence_of :sku, :currency
|
201
|
-
validates_uniqueness_of :sku
|
202
|
-
end
|
186
|
+
composed_of :price,
|
187
|
+
:class_name => "Money",
|
188
|
+
:mapping => [%w(cents cents), %w(currency currency_as_string)],
|
189
|
+
:constructor => Proc.new { |cents, currency| Money.new(cents || 0, currency || Money.default_currency) }
|
203
190
|
|
204
191
|
For Money 2.2.x and previous versions, simply use the following
|
205
192
|
<tt>composed_of</tt> definition:
|
@@ -209,4 +196,4 @@ For Money 2.2.x and previous versions, simply use the following
|
|
209
196
|
:mapping => [%w(cents cents), %w(currency currency)],
|
210
197
|
:constructor => Proc.new { |cents, currency| Money.new(cents || 0, currency || Money.default_currency) }
|
211
198
|
|
212
|
-
For further details read the full discussion at http://github.com/
|
199
|
+
For further details read the full discussion at http://github.com/RubyMoney/money/issues/4#comment_224880
|
data/Rakefile
CHANGED
@@ -12,7 +12,7 @@ begin
|
|
12
12
|
gem.description = "Money and currency exchange support library."
|
13
13
|
gem.email = "hongli@phusion.nl"
|
14
14
|
gem.homepage = "http://money.rubyforge.org/"
|
15
|
-
gem.authors = ["Tobias Luetke", "Hongli Lai", "Jeremy McNevin", "Shane Emmons"]
|
15
|
+
gem.authors = ["Tobias Luetke", "Hongli Lai", "Jeremy McNevin", "Shane Emmons", "Simone Carletti"]
|
16
16
|
gem.rubyforge_project = "money"
|
17
17
|
gem.add_development_dependency "rspec", ">= 1.2.9"
|
18
18
|
gem.add_development_dependency "hanna", ">= 0.1.12"
|
@@ -26,10 +26,10 @@ rescue LoadError
|
|
26
26
|
end
|
27
27
|
|
28
28
|
require 'spec/rake/spectask'
|
29
|
-
Spec::Rake::SpecTask.new(:
|
30
|
-
spec.libs << 'lib' << '
|
31
|
-
spec.spec_files =
|
32
|
-
spec.spec_opts << '--
|
29
|
+
Spec::Rake::SpecTask.new(:test) do |spec|
|
30
|
+
spec.libs << 'lib' << 'spec'
|
31
|
+
spec.spec_files = Dir['spec/**/*_spec.rb']
|
32
|
+
spec.spec_opts << '--color'
|
33
33
|
end
|
34
34
|
|
35
35
|
task :spec => :check_dependencies
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.0.
|
1
|
+
3.1.0.pre1
|
data/lib/money.rb
CHANGED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
class Money
|
4
|
+
module Bank
|
5
|
+
|
6
|
+
# The lowest Money::Bank error class.
|
7
|
+
# All Money::Bank errors should inherit from it.
|
8
|
+
class Error < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
# Raised when the bank doesn't know about the conversion rate
|
12
|
+
# for specified currencies.
|
13
|
+
class UnknownRate < Error
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
#
|
18
|
+
# Money::Bank::Base is the basic interface for creating a money exchange object,
|
19
|
+
# also called Bank.
|
20
|
+
#
|
21
|
+
# A Bank is responsible for storing exchange rates,
|
22
|
+
# take a Money object as input and returns the corresponding Money object
|
23
|
+
# converted into an other currency.
|
24
|
+
#
|
25
|
+
# This class exists for aiding in the creating of other classes to exchange money between
|
26
|
+
# different currencies. When creating a subclass you will need to implement
|
27
|
+
# the following methods to exchange money between currencies:
|
28
|
+
#
|
29
|
+
# * #exchange_with(Money) # => Money
|
30
|
+
#
|
31
|
+
# See Money::Bank::VariableExchange for a real example.
|
32
|
+
#
|
33
|
+
# Also, you can extend Money::Bank::VariableExchange
|
34
|
+
# instead of Money::Bank::Base if your bank implementation
|
35
|
+
# needs to store rates internally.
|
36
|
+
#
|
37
|
+
class Base
|
38
|
+
|
39
|
+
# Returns the singleton instance of the Base bank.
|
40
|
+
def self.instance
|
41
|
+
@@singleton ||= self.new
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
# @deprecated +#exchange+ will be removed in v3.2.0, use +#exchange_with+
|
46
|
+
#
|
47
|
+
# Exchanges the given amount of cents in +from_currency+ to +to_currency+.
|
48
|
+
#
|
49
|
+
# Returns the amount of cents in +to_currency+ as an integer, rounded down.
|
50
|
+
def exchange(cents, from_currency, to_currency, &block)
|
51
|
+
Money.deprecate "`Money::Bank::Base#exchange' will be removed in v3.2.0, use #exchange_with instead"
|
52
|
+
exchange_with(Money.new(cents, from_currency), to_currency, &block).cents
|
53
|
+
end
|
54
|
+
|
55
|
+
# Exchanges the given +Money+ object to a new +Money+ object in
|
56
|
+
# +to_currency+.
|
57
|
+
#
|
58
|
+
# You should implement this in a subclass,
|
59
|
+
# otherwise it will throw a NotImplementedError as a reminder.
|
60
|
+
#
|
61
|
+
# Returns a new +Money+ object.
|
62
|
+
# Raises <tt>NotImplementedError</tt>.
|
63
|
+
def exchange_with(from, to_currency, &block)
|
64
|
+
raise NotImplementedError, "#exchange_with must be implemented"
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
# Given two currency strings or object,
|
69
|
+
# checks whether they're both the same currency.
|
70
|
+
#
|
71
|
+
# same_currency?("usd", "USD") # => true
|
72
|
+
# same_currency?("usd", "EUR") # => false
|
73
|
+
# same_currency?("usd", Currency.new("USD") # => true
|
74
|
+
# same_currency?("usd", "USD") # => true
|
75
|
+
#
|
76
|
+
# Return +true+ if the currencies are the same, +false+ otherwise.
|
77
|
+
def same_currency?(currency1, currency2)
|
78
|
+
Currency.wrap(currency1) == Currency.wrap(currency2)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'money/bank/base'
|
2
|
+
|
3
|
+
class Money
|
4
|
+
module Bank
|
5
|
+
|
6
|
+
# Class for aiding in exchanging money between different currencies.
|
7
|
+
# By default, the Money class uses an object of this class (accessible through
|
8
|
+
# Money#bank) for performing currency exchanges.
|
9
|
+
#
|
10
|
+
# By default, Bank::VariableExchange has no knowledge about conversion rates.
|
11
|
+
# One must manually specify them with +add_rate+, after which one can perform
|
12
|
+
# exchanges with +exchange+. For example:
|
13
|
+
#
|
14
|
+
# bank = Money::Bank::VariableExchange.new
|
15
|
+
# bank.add_rate("USD", "CAD", 1.24515)
|
16
|
+
# bank.add_rate("CAD", "USD", 0.803115)
|
17
|
+
#
|
18
|
+
# # Exchange 100 CAD to USD:
|
19
|
+
# bank.exchange(100_00, "CAD", "USD") # => 124
|
20
|
+
# # Exchange 100 USD to CAD:
|
21
|
+
# bank.exchange(100_00, "USD", "CAD") # => 80
|
22
|
+
#
|
23
|
+
class VariableExchange < Base
|
24
|
+
|
25
|
+
def initialize(&block)
|
26
|
+
@rates = {}
|
27
|
+
@mutex = Mutex.new
|
28
|
+
@rounding_method = block
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# Exchanges the given +Money+ object to a new +Money+ object in
|
33
|
+
# +to_currency+. Returns a new +Money+ object.
|
34
|
+
#
|
35
|
+
# Raises <tt>Money::Bank::UnknownRate</tt> if the conversion rate is unknown.
|
36
|
+
def exchange_with(from, to_currency, &block)
|
37
|
+
return from if same_currency?(from.currency, to_currency)
|
38
|
+
|
39
|
+
rate = get_rate(from.currency, to_currency)
|
40
|
+
unless rate
|
41
|
+
raise UnknownRate, "No conversion rate known for '#{from.currency.iso_code}' -> '#{to_currency}'"
|
42
|
+
end
|
43
|
+
_to_currency_ = Currency.wrap(to_currency)
|
44
|
+
|
45
|
+
cents = from.cents / (from.currency.subunit_to_unit.to_f / _to_currency_.subunit_to_unit.to_f)
|
46
|
+
|
47
|
+
ex = cents * rate
|
48
|
+
ex = if block_given?
|
49
|
+
block.call(ex)
|
50
|
+
elsif @rounding_method
|
51
|
+
@rounding_method.call(ex)
|
52
|
+
else
|
53
|
+
ex.to_s.to_i
|
54
|
+
end
|
55
|
+
Money.new(ex, _to_currency_)
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
# Registers a conversion rate. +from+ and +to+ are both currency names or
|
60
|
+
# +Currency+ objects.
|
61
|
+
def add_rate(from, to, rate)
|
62
|
+
set_rate(from, to, rate)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Set the rate for the given currencies.
|
66
|
+
def set_rate(from, to, rate)
|
67
|
+
@mutex.synchronize { @rates[rate_key_for(from, to)] = rate }
|
68
|
+
end
|
69
|
+
|
70
|
+
# Retrieve the rate for the given currencies.
|
71
|
+
def get_rate(from, to)
|
72
|
+
@mutex.synchronize { @rates[rate_key_for(from, to)] }
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
# Return the rate hashkey for the given currencies.
|
78
|
+
def rate_key_for(from, to)
|
79
|
+
"#{Currency.wrap(from).iso_code}_TO_#{Currency.wrap(to).iso_code}".upcase
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -8,7 +8,14 @@ class Numeric
|
|
8
8
|
# BigDecimal.new('100').to_money => #<Money @cents=10000>
|
9
9
|
def to_money(currency = Money.default_currency)
|
10
10
|
currency = Money::Currency.new(currency) unless currency.is_a?(Money::Currency)
|
11
|
-
|
11
|
+
amt = self * currency.subunit_to_unit
|
12
|
+
amt = case amt.class.to_s
|
13
|
+
when 'BigDecimal'
|
14
|
+
amt.to_s('F')
|
15
|
+
else
|
16
|
+
amt.to_s
|
17
|
+
end
|
18
|
+
Money.new(amt.to_i, currency)
|
12
19
|
end
|
13
20
|
end
|
14
21
|
|
data/lib/money/currency.rb
CHANGED
@@ -217,6 +217,21 @@ class Money
|
|
217
217
|
self.id == other_currency.id
|
218
218
|
end
|
219
219
|
|
220
|
+
# synonymous with #==
|
221
|
+
def eql?(other_currency)
|
222
|
+
self == other_currency
|
223
|
+
end
|
224
|
+
|
225
|
+
# Returns a Fixnum hash value based on the <tt>id</tt> attribute
|
226
|
+
# in order to use functions like & (intersection), group_by, etc.
|
227
|
+
#
|
228
|
+
# [Currency.new(:usd), Currency.new(:eur)] & [Currency.new(:usd)]
|
229
|
+
# # => [Currency.new(:usd)]
|
230
|
+
#
|
231
|
+
def hash
|
232
|
+
id.hash
|
233
|
+
end
|
234
|
+
|
220
235
|
# Returns a string representation
|
221
236
|
# corresponding to the upcase <tt>id</tt> attribute.
|
222
237
|
#
|
@@ -242,7 +257,7 @@ class Money
|
|
242
257
|
|
243
258
|
|
244
259
|
def method_missing(method, *args, &block)
|
245
|
-
|
260
|
+
Money.deprecate "`currency' is now a Currency instance. Call `currency.to_s.#{method}' instead."
|
246
261
|
iso_code.send(method, *args, &block)
|
247
262
|
end
|
248
263
|
|
data/lib/money/defaults.rb
CHANGED
@@ -10,21 +10,15 @@ class Money
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def [](key)
|
13
|
-
deprecate
|
13
|
+
Money.deprecate(@message)
|
14
14
|
super
|
15
15
|
end
|
16
16
|
|
17
17
|
def []=(value)
|
18
|
-
deprecate
|
18
|
+
Money.deprecate(@message)
|
19
19
|
super
|
20
20
|
end
|
21
21
|
|
22
|
-
private
|
23
|
-
|
24
|
-
def deprecate
|
25
|
-
warn "DEPRECATION MESSAGE: #{@message}"
|
26
|
-
end
|
27
|
-
|
28
22
|
end
|
29
23
|
|
30
24
|
# @deprecated See Money::Currency#symbol
|
@@ -42,16 +36,16 @@ class Money
|
|
42
36
|
"GHC" => "¢",
|
43
37
|
"BRL" => "R$ ",
|
44
38
|
# Everything else defaults to '¤'
|
45
|
-
}, "Money::SYMBOLS has no longer effect. See Money::Currency#symbol.")
|
39
|
+
}, "Money::SYMBOLS has no longer effect and will be removed in v3.2.0. See Money::Currency#symbol.")
|
46
40
|
|
47
41
|
SEPARATORS = DeprecatedHash.new({
|
48
42
|
"BRL" => ",",
|
49
43
|
# Everything else defaults to '.'
|
50
|
-
}, "Money::SEPARATORS is deprecated. See Money::Currency#separator.")
|
44
|
+
}, "Money::SEPARATORS is deprecated and will be removed in v3.2.0. See Money::Currency#separator.")
|
51
45
|
|
52
46
|
DELIMITERS = DeprecatedHash.new({
|
53
47
|
"BRL" => ".",
|
54
48
|
# Everything else defaults to ","
|
55
|
-
}, "Money::DELIMITERS is deprecated. See Money::Currency#delimiter.")
|
49
|
+
}, "Money::DELIMITERS is deprecated and will be removed in Money v3.2.0. See Money::Currency#delimiter.")
|
56
50
|
|
57
51
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Money
|
2
|
+
|
3
|
+
def self.deprecate(message)
|
4
|
+
warn "DEPRECATION WARNING: #{message}"
|
5
|
+
end
|
6
|
+
|
7
|
+
|
8
|
+
# Money::VariableExchangeBank is the legacy default bank
|
9
|
+
# shipped with Money. The class has been superseded by
|
10
|
+
# Money::Bank::VariableExchange.
|
11
|
+
#
|
12
|
+
# @deprecate Use Money::Bank::VariableExchange instead.
|
13
|
+
class VariableExchangeBank < Bank::VariableExchange # :nodoc:
|
14
|
+
def initialize(*args)
|
15
|
+
Money.deprecate "Money::VariableExchangeBank is deprecated and will be removed in v3.2.0. " +
|
16
|
+
"Use Money::Bank::VariableExchange instead."
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/lib/money/money.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'money/currency'
|
3
|
-
require 'money/
|
3
|
+
require 'money/bank/variable_exchange'
|
4
4
|
|
5
5
|
# Represents an amount of money in a certain currency.
|
6
6
|
class Money
|
@@ -25,7 +25,7 @@ class Money
|
|
25
25
|
# money2.bank # => bank2
|
26
26
|
# money1.bank # => bank1
|
27
27
|
#
|
28
|
-
# The default value for this property is an instance if
|
28
|
+
# The default value for this property is an instance if Bank::VariableExchange.
|
29
29
|
# It allows one to specify custom exchange rates:
|
30
30
|
#
|
31
31
|
# Money.default_bank.add_rate("USD", "CAD", 1.24515)
|
@@ -40,7 +40,7 @@ class Money
|
|
40
40
|
attr_accessor :default_currency
|
41
41
|
end
|
42
42
|
|
43
|
-
self.default_bank =
|
43
|
+
self.default_bank = Bank::VariableExchange.instance
|
44
44
|
self.default_currency = Currency.new("USD")
|
45
45
|
|
46
46
|
|
@@ -106,7 +106,7 @@ class Money
|
|
106
106
|
def ==(other_money)
|
107
107
|
if other_money.respond_to?(:to_money)
|
108
108
|
other_money = other_money.to_money
|
109
|
-
cents == other_money.cents &&
|
109
|
+
cents == other_money.cents && self.currency == other_money.currency
|
110
110
|
else
|
111
111
|
false
|
112
112
|
end
|
@@ -117,6 +117,17 @@ class Money
|
|
117
117
|
self == other_money
|
118
118
|
end
|
119
119
|
|
120
|
+
# Returns a Fixnum hash value based on the <tt>cents</tt> and
|
121
|
+
# <tt>currency</tt> attributes in order to use functions like
|
122
|
+
# & (intersection), group_by, etc.
|
123
|
+
#
|
124
|
+
# [Money.new(1_00, :eur), Money.new(2_00, :usd)] & [Money.new(1_00, :eur)]
|
125
|
+
# # => [Money.new(1_00, :eur)]
|
126
|
+
#
|
127
|
+
def hash
|
128
|
+
[cents.hash, currency.hash].hash
|
129
|
+
end
|
130
|
+
|
120
131
|
# Compares this money object against another object. +other_money+ must respond
|
121
132
|
# to #to_money.
|
122
133
|
#
|
@@ -128,7 +139,7 @@ class Money
|
|
128
139
|
def <=>(other_money)
|
129
140
|
if other_money.respond_to?(:to_money)
|
130
141
|
other_money = other_money.to_money
|
131
|
-
if
|
142
|
+
if self.currency == other_money.currency
|
132
143
|
cents <=> other_money.cents
|
133
144
|
else
|
134
145
|
cents <=> other_money.exchange_to(currency).cents
|
@@ -444,7 +455,7 @@ class Money
|
|
444
455
|
#
|
445
456
|
def exchange_to(other_currency)
|
446
457
|
other_currency = Currency.wrap(other_currency)
|
447
|
-
|
458
|
+
@bank.exchange_with(self, other_currency)
|
448
459
|
end
|
449
460
|
|
450
461
|
# Receive a money object with the same amount as the current Money object
|
@@ -484,4 +495,5 @@ class Money
|
|
484
495
|
end
|
485
496
|
rules
|
486
497
|
end
|
498
|
+
|
487
499
|
end
|
data/money.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{money}
|
8
|
-
s.version = "3.0.
|
8
|
+
s.version = "3.1.0.pre1"
|
9
9
|
|
10
|
-
s.required_rubygems_version = Gem::Requirement.new("
|
11
|
-
s.authors = ["Tobias Luetke", "Hongli Lai", "Jeremy McNevin", "Shane Emmons"]
|
12
|
-
s.date = %q{2010-
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Tobias Luetke", "Hongli Lai", "Jeremy McNevin", "Shane Emmons", "Simone Carletti"]
|
12
|
+
s.date = %q{2010-08-03}
|
13
13
|
s.description = %q{Money and currency exchange support library.}
|
14
14
|
s.email = %q{hongli@phusion.nl}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -26,17 +26,21 @@ Gem::Specification.new do |s|
|
|
26
26
|
"Rakefile",
|
27
27
|
"VERSION",
|
28
28
|
"lib/money.rb",
|
29
|
+
"lib/money/bank/base.rb",
|
30
|
+
"lib/money/bank/variable_exchange.rb",
|
29
31
|
"lib/money/core_extensions.rb",
|
30
32
|
"lib/money/currency.rb",
|
31
33
|
"lib/money/defaults.rb",
|
32
|
-
"lib/money/
|
34
|
+
"lib/money/deprecations.rb",
|
33
35
|
"lib/money/money.rb",
|
34
|
-
"lib/money/variable_exchange_bank.rb",
|
35
36
|
"money.gemspec",
|
36
|
-
"
|
37
|
-
"
|
38
|
-
"
|
39
|
-
"
|
37
|
+
"spec/bank/base_spec.rb",
|
38
|
+
"spec/bank/variable_exchange_spec.rb",
|
39
|
+
"spec/core_extensions_spec.rb",
|
40
|
+
"spec/currency_spec.rb",
|
41
|
+
"spec/deprecations_spec.rb",
|
42
|
+
"spec/money_spec.rb",
|
43
|
+
"spec/spec_helper.rb"
|
40
44
|
]
|
41
45
|
s.homepage = %q{http://money.rubyforge.org/}
|
42
46
|
s.rdoc_options = ["--charset=UTF-8"]
|
@@ -45,10 +49,13 @@ Gem::Specification.new do |s|
|
|
45
49
|
s.rubygems_version = %q{1.3.7}
|
46
50
|
s.summary = %q{Money and currency exchange support library}
|
47
51
|
s.test_files = [
|
48
|
-
"
|
49
|
-
"
|
50
|
-
"
|
51
|
-
"
|
52
|
+
"spec/bank/base_spec.rb",
|
53
|
+
"spec/bank/variable_exchange_spec.rb",
|
54
|
+
"spec/core_extensions_spec.rb",
|
55
|
+
"spec/currency_spec.rb",
|
56
|
+
"spec/deprecations_spec.rb",
|
57
|
+
"spec/money_spec.rb",
|
58
|
+
"spec/spec_helper.rb"
|
52
59
|
]
|
53
60
|
|
54
61
|
if s.respond_to? :specification_version then
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Money::Bank::Base do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
@bank = Money::Bank::Base.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#exchange' do
|
10
|
+
it 'should raise NotImplementedError' do
|
11
|
+
lambda { @bank.exchange(100, 'USD', 'EUR') }.should raise_exception(NotImplementedError)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#exchange_with' do
|
16
|
+
it 'should raise NotImplementedError' do
|
17
|
+
lambda { @bank.exchange_with(Money.new(100, 'USD'), 'EUR') }.should raise_exception(NotImplementedError)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#same_currency?' do
|
22
|
+
it 'should accept str/str' do
|
23
|
+
lambda{@bank.send(:same_currency?, 'USD', 'EUR')}.should_not raise_exception
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should accept currency/str' do
|
27
|
+
lambda{@bank.send(:same_currency?, Money::Currency.wrap('USD'), 'EUR')}.should_not raise_exception
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should accept str/currency' do
|
31
|
+
lambda{@bank.send(:same_currency?, 'USD', Money::Currency.wrap('EUR'))}.should_not raise_exception
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should accept currency/currency' do
|
35
|
+
lambda{@bank.send(:same_currency?, Money::Currency.wrap('USD'), Money::Currency.wrap('EUR'))}.should_not raise_exception
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should return `true` when currencies match' do
|
39
|
+
@bank.send(:same_currency?, 'USD', 'USD').should == true
|
40
|
+
@bank.send(:same_currency?, Money::Currency.wrap('USD'), 'USD').should == true
|
41
|
+
@bank.send(:same_currency?, 'USD', Money::Currency.wrap('USD')).should == true
|
42
|
+
@bank.send(:same_currency?, Money::Currency.wrap('USD'), Money::Currency.wrap('USD')).should == true
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should return `false` when currencies do not match' do
|
46
|
+
@bank.send(:same_currency?, 'USD', 'EUR').should == false
|
47
|
+
@bank.send(:same_currency?, Money::Currency.wrap('USD'), 'EUR').should == false
|
48
|
+
@bank.send(:same_currency?, 'USD', Money::Currency.wrap('EUR')).should == false
|
49
|
+
@bank.send(:same_currency?, Money::Currency.wrap('USD'), Money::Currency.wrap('EUR')).should == false
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should raise an UnknownCurrency exception when an unknown currency is passed' do
|
53
|
+
lambda{@bank.send(:same_currency?, 'AAA', 'BBB')}.should raise_exception(Money::Currency::UnknownCurrency)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Money::Bank::VariableExchange do
|
4
|
+
|
5
|
+
describe '#new without block' do
|
6
|
+
before :each do
|
7
|
+
@bank = Money::Bank::VariableExchange.new
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#exchange' do
|
11
|
+
before :each do
|
12
|
+
@bank.send(:set_rate, 'USD', 'EUR', 1.33)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should accept str/str' do
|
16
|
+
lambda{@bank.exchange(100, 'USD', 'EUR')}.should_not raise_exception
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should accept currency/str' do
|
20
|
+
lambda{@bank.exchange(100, Money::Currency.wrap('USD'), 'EUR')}.should_not raise_exception
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should accept str/currency' do
|
24
|
+
lambda{@bank.exchange(100, 'USD', Money::Currency.wrap('EUR'))}.should_not raise_exception
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should accept currency/currency' do
|
28
|
+
lambda{@bank.exchange(100, Money::Currency.wrap('USD'), Money::Currency.wrap('EUR'))}.should_not raise_exception
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should exchange one currency to another' do
|
32
|
+
@bank.exchange(100, 'USD', 'EUR').should == 133
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should truncate extra digits' do
|
36
|
+
@bank.exchange(10, 'USD', 'EUR').should == 13
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should raise an UnknownCurrency exception when an unknown currency is requested' do
|
40
|
+
lambda{@bank.exchange(100, 'AAA', 'BBB')}.should raise_exception(Money::Currency::UnknownCurrency)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should raise an UnknownRate exception when an unknown rate is requested' do
|
44
|
+
lambda{@bank.exchange(100, 'USD', 'JPY')}.should raise_exception(Money::Bank::UnknownRate)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should round the exchanged result down' do
|
48
|
+
@bank.add_rate("USD", "EUR", 0.788332676)
|
49
|
+
@bank.add_rate("EUR", "YEN", 122.631477)
|
50
|
+
@bank.exchange(10_00, "USD", "EUR").should == 788
|
51
|
+
@bank.exchange(500_00, "EUR", "YEN").should == 6131573
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should accept a custom truncation method' do
|
55
|
+
proc = Proc.new { |n| n.ceil }
|
56
|
+
@bank.exchange(10, 'USD', 'EUR', &proc).should == 14
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
context 'sterling to euros using a rate of 1.39' do
|
61
|
+
it 'returns the correct amount' do
|
62
|
+
@bank.add_rate('GBP', 'EUR', 1.38)
|
63
|
+
@bank.exchange(10000, 'GBP', 'EUR').should == 13800
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'dollars to euros using a rate of 0.86' do
|
68
|
+
it 'returns the correct amount' do
|
69
|
+
@bank.add_rate('USD', 'EUR', 0.86)
|
70
|
+
@bank.exchange(10000, 'USD', 'EUR').should == 8600
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'TND to USD using a rate of 0.67138' do
|
75
|
+
it 'returns the correct amount' do
|
76
|
+
@bank.add_rate('TND', 'USD', 0.67138)
|
77
|
+
@bank.exchange(1000, 'TND', 'USD').should == 67
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'USD to TND using a rate of 1.32862' do
|
82
|
+
it 'returns the correct amount' do
|
83
|
+
@bank.add_rate('USD', 'TND', 1.32862)
|
84
|
+
@bank.exchange(1000, 'USD', 'TND').should == 13286
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#exchange_with' do
|
90
|
+
before :each do
|
91
|
+
@bank.send(:set_rate, 'USD', 'EUR', 1.33)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should accept str' do
|
95
|
+
lambda{@bank.exchange_with(Money.new(100, 'USD'), 'EUR')}.should_not raise_exception
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should accept currency' do
|
99
|
+
lambda{@bank.exchange_with(Money.new(100, 'USD'), Money::Currency.wrap('EUR'))}.should_not raise_exception
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should exchange one currency to another' do
|
103
|
+
@bank.exchange_with(Money.new(100, 'USD'), 'EUR').should == Money.new(133, 'EUR')
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should truncate extra digits' do
|
107
|
+
@bank.exchange_with(Money.new(10, 'USD'), 'EUR').should == Money.new(13, 'EUR')
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should raise an UnknownCurrency exception when an unknown currency is requested' do
|
111
|
+
lambda{@bank.exchange_with(Money.new(100, 'USD'), 'BBB')}.should raise_exception(Money::Currency::UnknownCurrency)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should raise an UnknownRate exception when an unknown rate is requested' do
|
115
|
+
lambda{@bank.exchange_with(Money.new(100, 'USD'), 'JPY')}.should raise_exception(Money::Bank::UnknownRate)
|
116
|
+
end
|
117
|
+
|
118
|
+
#it 'should round the exchanged result down' do
|
119
|
+
# @bank.add_rate("USD", "EUR", 0.788332676)
|
120
|
+
# @bank.add_rate("EUR", "YEN", 122.631477)
|
121
|
+
# @bank.exchange_with(Money.new(10_00, "USD"), "EUR").should == Money.new(788, "EUR")
|
122
|
+
# @bank.exchange_with(Money.new(500_00, "EUR"), "YEN").should == Money.new(6131573, "YEN")
|
123
|
+
#end
|
124
|
+
|
125
|
+
it 'should accept a custom truncation method' do
|
126
|
+
proc = Proc.new { |n| n.ceil }
|
127
|
+
@bank.exchange_with(Money.new(10, 'USD'), 'EUR', &proc).should == Money.new(14, 'EUR')
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "#add_rate" do
|
132
|
+
it "should add rates correctly" do
|
133
|
+
@bank.add_rate("USD", "EUR", 0.788332676)
|
134
|
+
@bank.add_rate("EUR", "YEN", 122.631477)
|
135
|
+
|
136
|
+
@bank.instance_variable_get(:@rates)['USD_TO_EUR'].should == 0.788332676
|
137
|
+
@bank.instance_variable_get(:@rates)['EUR_TO_JPY'].should == 122.631477
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should treat currency names case-insensitively" do
|
141
|
+
@bank.add_rate("usd", "eur", 1)
|
142
|
+
@bank.instance_variable_get(:@rates)['USD_TO_EUR'].should == 1
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe '#set_rate' do
|
147
|
+
it 'should set a rate' do
|
148
|
+
@bank.set_rate('USD', 'EUR', 1.25)
|
149
|
+
@bank.instance_variable_get(:@rates)['USD_TO_EUR'].should == 1.25
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should raise an UnknownCurrency exception when an unknown currency is passed' do
|
153
|
+
lambda{ @bank.set_rate('AAA', 'BBB', 1.25) }.should raise_exception(Money::Currency::UnknownCurrency)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe '#get_rate' do
|
158
|
+
it 'should return a rate' do
|
159
|
+
@bank.set_rate('USD', 'EUR', 1.25)
|
160
|
+
@bank.get_rate('USD', 'EUR').should == 1.25
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'should raise an UnknownCurrency exception when an unknown currency is requested' do
|
164
|
+
lambda{ @bank.get_rate('AAA', 'BBB') }.should raise_exception(Money::Currency::UnknownCurrency)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe '#rate_key_for' do
|
169
|
+
it 'should accept str/str' do
|
170
|
+
lambda{@bank.send(:rate_key_for, 'USD', 'EUR')}.should_not raise_exception
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'should accept currency/str' do
|
174
|
+
lambda{@bank.send(:rate_key_for, Money::Currency.wrap('USD'), 'EUR')}.should_not raise_exception
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'should accept str/currency' do
|
178
|
+
lambda{@bank.send(:rate_key_for, 'USD', Money::Currency.wrap('EUR'))}.should_not raise_exception
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'should accept currency/currency' do
|
182
|
+
lambda{@bank.send(:rate_key_for, Money::Currency.wrap('USD'), Money::Currency.wrap('EUR'))}.should_not raise_exception
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'should return a hashkey based on the passed arguments' do
|
186
|
+
@bank.send(:rate_key_for, 'USD', 'EUR').should == 'USD_TO_EUR'
|
187
|
+
@bank.send(:rate_key_for, Money::Currency.wrap('USD'), 'EUR').should == 'USD_TO_EUR'
|
188
|
+
@bank.send(:rate_key_for, 'USD', Money::Currency.wrap('EUR')).should == 'USD_TO_EUR'
|
189
|
+
@bank.send(:rate_key_for, Money::Currency.wrap('USD'), Money::Currency.wrap('EUR')).should == 'USD_TO_EUR'
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'should raise an UnknownCurrency exception when an unknown currency is passed' do
|
193
|
+
lambda{@bank.send(:rate_key_for, 'AAA', 'BBB')}.should raise_exception(Money::Currency::UnknownCurrency)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
describe '#new with &block' do
|
201
|
+
before :each do
|
202
|
+
proc = Proc.new { |n| n.ceil }
|
203
|
+
@bank = Money::Bank::VariableExchange.new(&proc)
|
204
|
+
@bank.add_rate('USD', 'EUR', 1.33)
|
205
|
+
end
|
206
|
+
|
207
|
+
describe '#exchange' do
|
208
|
+
it 'should use a stored truncation method' do
|
209
|
+
@bank.exchange(10, 'USD', 'EUR').should == 14
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'should use a custom truncation method over a stored one' do
|
213
|
+
proc = Proc.new { |n| n.ceil + 1 }
|
214
|
+
@bank.exchange(10, 'USD', 'EUR', &proc).should == 15
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
describe '#exchange_with' do
|
219
|
+
it 'should use a stored truncation method' do
|
220
|
+
@bank.exchange_with(Money.new(10, 'USD'), 'EUR').should == Money.new(14, 'EUR')
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'should use a custom truncation method over a stored one' do
|
224
|
+
proc = Proc.new { |n| n.ceil + 1 }
|
225
|
+
@bank.exchange_with(Money.new(10, 'USD'), 'EUR', &proc).should == Money.new(15, 'EUR')
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
@@ -1,6 +1,4 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'money/core_extensions'
|
1
|
+
require "spec_helper"
|
4
2
|
|
5
3
|
describe "Money core extensions" do
|
6
4
|
specify "Numberic#to_money works" do
|
@@ -17,6 +15,11 @@ describe "Money core extensions" do
|
|
17
15
|
money.cents.should == 1234_00
|
18
16
|
money.currency.should == Money.default_currency
|
19
17
|
end
|
18
|
+
|
19
|
+
specify "#issue/15" do
|
20
|
+
amount = 555.55.to_money
|
21
|
+
amount.should == Money.new(55555)
|
22
|
+
end
|
20
23
|
|
21
24
|
specify "Numeric#to_money accepts optional currency" do
|
22
25
|
1234.to_money('USD').should == Money.new(123400, 'USD')
|
@@ -1,10 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
require 'money/money'
|
6
|
-
require 'money/currency'
|
7
|
-
require 'money/defaults'
|
3
|
+
require "spec_helper"
|
8
4
|
|
9
5
|
describe Money::Currency do
|
10
6
|
|
@@ -36,6 +32,21 @@ describe Money::Currency do
|
|
36
32
|
Money::Currency.new(:eur).should == Money::Currency.new(:eur)
|
37
33
|
Money::Currency.new(:eur).should_not == Money::Currency.new(:usd)
|
38
34
|
end
|
35
|
+
|
36
|
+
specify "#eql? should return true if #== returns true" do
|
37
|
+
Money::Currency.new(:eur).eql?(Money::Currency.new(:eur)).should be true
|
38
|
+
Money::Currency.new(:eur).eql?(Money::Currency.new(:usd)).should be false
|
39
|
+
end
|
40
|
+
|
41
|
+
specify "#hash should return the same value for equal objects" do
|
42
|
+
Money::Currency.new(:eur).hash.should == Money::Currency.new(:eur).hash
|
43
|
+
Money::Currency.new(:eur).hash.should_not == Money::Currency.new(:usd).hash
|
44
|
+
end
|
45
|
+
|
46
|
+
specify "#hash can be used to return the intersection of Currency object arrays" do
|
47
|
+
intersection = [Money::Currency.new(:eur), Money::Currency.new(:usd)] & [Money::Currency.new(:eur)]
|
48
|
+
intersection.should == [Money::Currency.new(:eur)]
|
49
|
+
end
|
39
50
|
|
40
51
|
specify "#<=> should compare objects by priority" do
|
41
52
|
Money::Currency.new(:cad).should > Money::Currency.new(:usd)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Money deprecations" do
|
4
|
+
|
5
|
+
describe Money::VariableExchangeBank do
|
6
|
+
it "should be deprecated" do
|
7
|
+
Money.should_receive(:deprecate)
|
8
|
+
Money::VariableExchangeBank.new.should_not be_nil
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should extend Money::Bank::VariableExchange" do
|
12
|
+
Money::VariableExchangeBank.new.should be_kind_of Money::Bank::VariableExchange
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/{test → spec}/money_spec.rb
RENAMED
@@ -1,11 +1,10 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
3
|
-
require
|
4
|
-
require 'money/defaults'
|
2
|
+
|
3
|
+
require "spec_helper"
|
5
4
|
|
6
5
|
describe Money do
|
7
|
-
it "is associated to the singleton instance of
|
8
|
-
Money.new(0).bank.
|
6
|
+
it "is associated to the singleton instance of Bank::VariableExchange by default" do
|
7
|
+
Money.new(0).bank.should be_equal Money::Bank::VariableExchange.instance
|
9
8
|
end
|
10
9
|
|
11
10
|
specify "#cents returns the amount of cents passed to the constructor" do
|
@@ -64,13 +63,13 @@ describe Money do
|
|
64
63
|
|
65
64
|
specify "#exchange_to exchanges the amount via its exchange bank" do
|
66
65
|
money = Money.new(100_00, "USD")
|
67
|
-
money.bank.should_receive(:
|
66
|
+
money.bank.should_receive(:exchange_with).with(Money.new(100_00, Money::Currency.new("USD")), Money::Currency.new("EUR")).and_return(Money.new(200_00, Money::Currency.new('EUR')))
|
68
67
|
money.exchange_to("EUR")
|
69
68
|
end
|
70
69
|
|
71
70
|
specify "#exchange_to exchanges the amount properly" do
|
72
71
|
money = Money.new(100_00, "USD")
|
73
|
-
money.bank.should_receive(:
|
72
|
+
money.bank.should_receive(:exchange_with).with(Money.new(100_00, Money::Currency.new("USD")), Money::Currency.new("EUR")).and_return(Money.new(200_00, Money::Currency.new('EUR')))
|
74
73
|
money.exchange_to("EUR").should == Money.new(200_00, "EUR")
|
75
74
|
end
|
76
75
|
|
@@ -163,6 +162,19 @@ describe Money do
|
|
163
162
|
Money.new(1_00, "USD").eql?(/foo/).should be false
|
164
163
|
Money.new(1_00, "USD").eql?(nil).should be false
|
165
164
|
end
|
165
|
+
|
166
|
+
specify "#hash should return the same value for equal objects" do
|
167
|
+
Money.new(1_00, :eur).hash.should == Money.new(1_00, :eur).hash
|
168
|
+
Money.new(2_00, :usd).hash.should == Money.new(2_00, :usd).hash
|
169
|
+
Money.new(1_00, :eur).hash.should_not == Money.new(2_00, :eur).hash
|
170
|
+
Money.new(1_00, :eur).hash.should_not == Money.new(1_00, :usd).hash
|
171
|
+
Money.new(1_00, :eur).hash.should_not == Money.new(2_00, :usd).hash
|
172
|
+
end
|
173
|
+
|
174
|
+
specify "#hash can be used to return the intersection of Money object arrays" do
|
175
|
+
intersection = [Money.new(1_00, :eur), Money.new(1_00, :usd)] & [Money.new(1_00, :eur)]
|
176
|
+
intersection.should == [Money.new(1_00, :eur)]
|
177
|
+
end
|
166
178
|
|
167
179
|
specify "#<=> can be used to compare with a String money value" do
|
168
180
|
(Money.new(1_00) <=> "1.00").should == 0
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,24 +1,26 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: money
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 33582789
|
5
|
+
prerelease: true
|
6
6
|
segments:
|
7
7
|
- 3
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
-
|
10
|
-
version: 3.0.
|
10
|
+
- pre1
|
11
|
+
version: 3.1.0.pre1
|
11
12
|
platform: ruby
|
12
13
|
authors:
|
13
14
|
- Tobias Luetke
|
14
15
|
- Hongli Lai
|
15
16
|
- Jeremy McNevin
|
16
17
|
- Shane Emmons
|
18
|
+
- Simone Carletti
|
17
19
|
autorequire:
|
18
20
|
bindir: bin
|
19
21
|
cert_chain: []
|
20
22
|
|
21
|
-
date: 2010-
|
23
|
+
date: 2010-08-03 00:00:00 -04:00
|
22
24
|
default_executable:
|
23
25
|
dependencies:
|
24
26
|
- !ruby/object:Gem::Dependency
|
@@ -72,17 +74,21 @@ files:
|
|
72
74
|
- Rakefile
|
73
75
|
- VERSION
|
74
76
|
- lib/money.rb
|
77
|
+
- lib/money/bank/base.rb
|
78
|
+
- lib/money/bank/variable_exchange.rb
|
75
79
|
- lib/money/core_extensions.rb
|
76
80
|
- lib/money/currency.rb
|
77
81
|
- lib/money/defaults.rb
|
78
|
-
- lib/money/
|
82
|
+
- lib/money/deprecations.rb
|
79
83
|
- lib/money/money.rb
|
80
|
-
- lib/money/variable_exchange_bank.rb
|
81
84
|
- money.gemspec
|
82
|
-
-
|
83
|
-
-
|
84
|
-
-
|
85
|
-
-
|
85
|
+
- spec/bank/base_spec.rb
|
86
|
+
- spec/bank/variable_exchange_spec.rb
|
87
|
+
- spec/core_extensions_spec.rb
|
88
|
+
- spec/currency_spec.rb
|
89
|
+
- spec/deprecations_spec.rb
|
90
|
+
- spec/money_spec.rb
|
91
|
+
- spec/spec_helper.rb
|
86
92
|
has_rdoc: true
|
87
93
|
homepage: http://money.rubyforge.org/
|
88
94
|
licenses: []
|
@@ -104,12 +110,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
104
110
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
111
|
none: false
|
106
112
|
requirements:
|
107
|
-
- - "
|
113
|
+
- - ">"
|
108
114
|
- !ruby/object:Gem::Version
|
109
|
-
hash:
|
115
|
+
hash: 25
|
110
116
|
segments:
|
111
|
-
-
|
112
|
-
|
117
|
+
- 1
|
118
|
+
- 3
|
119
|
+
- 1
|
120
|
+
version: 1.3.1
|
113
121
|
requirements: []
|
114
122
|
|
115
123
|
rubyforge_project: money
|
@@ -118,7 +126,10 @@ signing_key:
|
|
118
126
|
specification_version: 3
|
119
127
|
summary: Money and currency exchange support library
|
120
128
|
test_files:
|
121
|
-
-
|
122
|
-
-
|
123
|
-
-
|
124
|
-
-
|
129
|
+
- spec/bank/base_spec.rb
|
130
|
+
- spec/bank/variable_exchange_spec.rb
|
131
|
+
- spec/core_extensions_spec.rb
|
132
|
+
- spec/currency_spec.rb
|
133
|
+
- spec/deprecations_spec.rb
|
134
|
+
- spec/money_spec.rb
|
135
|
+
- spec/spec_helper.rb
|
data/lib/money/errors.rb
DELETED
@@ -1,82 +0,0 @@
|
|
1
|
-
require 'thread'
|
2
|
-
require 'money/errors'
|
3
|
-
|
4
|
-
# Class for aiding in exchanging money between different currencies.
|
5
|
-
# By default, the Money class uses an object of this class (accessible through
|
6
|
-
# Money#bank) for performing currency exchanges.
|
7
|
-
#
|
8
|
-
# By default, VariableExchangeBank has no knowledge about conversion rates.
|
9
|
-
# One must manually specify them with +add_rate+, after which one can perform
|
10
|
-
# exchanges with +exchange+. For example:
|
11
|
-
#
|
12
|
-
# bank = Money::VariableExchangeBank.new
|
13
|
-
# bank.add_rate("USD", "CAD", 1.24515)
|
14
|
-
# bank.add_rate("CAD", "USD", 0.803115)
|
15
|
-
#
|
16
|
-
# # Exchange 100 CAD to USD:
|
17
|
-
# bank.exchange(100_00, "CAD", "USD") # => 124
|
18
|
-
# # Exchange 100 USD to CAD:
|
19
|
-
# bank.exchange(100_00, "USD", "CAD") # => 80
|
20
|
-
#
|
21
|
-
class Money
|
22
|
-
class VariableExchangeBank
|
23
|
-
# Returns the singleton instance of VariableExchangeBank.
|
24
|
-
#
|
25
|
-
# By default, <tt>Money.default_bank</tt> returns the same object.
|
26
|
-
def self.instance
|
27
|
-
@@singleton
|
28
|
-
end
|
29
|
-
|
30
|
-
def initialize(&block)
|
31
|
-
@rates = {}
|
32
|
-
@mutex = Mutex.new
|
33
|
-
@rounding_method = block
|
34
|
-
end
|
35
|
-
|
36
|
-
# Registers a conversion rate. +from+ and +to+ are both currency names.
|
37
|
-
def add_rate(from, to, rate)
|
38
|
-
@mutex.synchronize do
|
39
|
-
@rates["#{from}_TO_#{to}".upcase] = rate
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# Gets the rate for exchanging the currency named +from+ to the currency
|
44
|
-
# named +to+. Returns nil if the rate is unknown.
|
45
|
-
def get_rate(from, to)
|
46
|
-
@mutex.synchronize do
|
47
|
-
@rates["#{from}_TO_#{to}".upcase]
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
# Given two currency names, checks whether they're both the same currency.
|
52
|
-
#
|
53
|
-
# bank = VariableExchangeBank.new
|
54
|
-
# bank.same_currency?("usd", "USD") # => true
|
55
|
-
# bank.same_currency?("usd", "EUR") # => false
|
56
|
-
def same_currency?(currency1, currency2)
|
57
|
-
Currency.wrap(currency1) == Currency.wrap(currency2)
|
58
|
-
end
|
59
|
-
|
60
|
-
# Exchange the given amount of cents in +from_currency+ to +to_currency+.
|
61
|
-
# Returns the amount of cents in +to_currency+ as an integer, rounded down.
|
62
|
-
#
|
63
|
-
# If the conversion rate is unknown, then Money::UnknownRate will be raised.
|
64
|
-
def exchange(cents, from_currency, to_currency, &block)
|
65
|
-
rate = get_rate(from_currency, to_currency)
|
66
|
-
if !rate
|
67
|
-
raise Money::UnknownRate, "No conversion rate known for '#{from_currency}' -> '#{to_currency}'"
|
68
|
-
end
|
69
|
-
_from_currency_ = Currency.wrap(from_currency)
|
70
|
-
_to_currency_ = Currency.wrap(to_currency)
|
71
|
-
|
72
|
-
_cents_ = cents / (_from_currency_.subunit_to_unit.to_f / _to_currency_.subunit_to_unit.to_f)
|
73
|
-
|
74
|
-
ex = _cents_ * rate
|
75
|
-
return block.call(ex) if block_given?
|
76
|
-
return @rounding_method.call(ex) unless @rounding_method.nil?
|
77
|
-
ex.to_s.to_i
|
78
|
-
end
|
79
|
-
|
80
|
-
@@singleton = VariableExchangeBank.new
|
81
|
-
end
|
82
|
-
end
|
data/test/exchange_bank_spec.rb
DELETED
@@ -1,101 +0,0 @@
|
|
1
|
-
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
|
2
|
-
require 'money/variable_exchange_bank'
|
3
|
-
|
4
|
-
describe Money::VariableExchangeBank do
|
5
|
-
before :each do
|
6
|
-
@bank = Money::VariableExchangeBank.new
|
7
|
-
end
|
8
|
-
|
9
|
-
it "returns the previously specified conversion rate" do
|
10
|
-
@bank.add_rate("USD", "EUR", 0.788332676)
|
11
|
-
@bank.add_rate("EUR", "YEN", 122.631477)
|
12
|
-
@bank.get_rate("USD", "EUR").should == 0.788332676
|
13
|
-
@bank.get_rate("EUR", "YEN").should == 122.631477
|
14
|
-
end
|
15
|
-
|
16
|
-
it "treats currency names case-insensitively" do
|
17
|
-
@bank.add_rate("usd", "eur", 1)
|
18
|
-
@bank.get_rate("USD", "EUR").should == 1
|
19
|
-
@bank.same_currency?("USD", "usd").should be_true
|
20
|
-
@bank.same_currency?("EUR", "usd").should be_false
|
21
|
-
end
|
22
|
-
|
23
|
-
it "returns nil if the conversion rate is unknown" do
|
24
|
-
@bank.get_rate("American Pesos", "EUR").should be_nil
|
25
|
-
end
|
26
|
-
|
27
|
-
it "exchanges money from one currency to another according to the specified conversion rates" do
|
28
|
-
@bank.add_rate("USD", "EUR", 0.5)
|
29
|
-
@bank.add_rate("EUR", "YEN", 10)
|
30
|
-
@bank.exchange(10_00, "USD", "EUR").should == 5_00
|
31
|
-
@bank.exchange(500_00, "EUR", "YEN").should == 5000_00
|
32
|
-
end
|
33
|
-
|
34
|
-
it "rounds the exchanged result down" do
|
35
|
-
@bank.add_rate("USD", "EUR", 0.788332676)
|
36
|
-
@bank.add_rate("EUR", "YEN", 122.631477)
|
37
|
-
@bank.exchange(10_00, "USD", "EUR").should == 788
|
38
|
-
@bank.exchange(500_00, "EUR", "YEN").should == 6131573
|
39
|
-
end
|
40
|
-
|
41
|
-
it "raises Money::UnknownRate upon conversion if the conversion rate is unknown" do
|
42
|
-
block = lambda { @bank.exchange(10, "USD", "EUR") }
|
43
|
-
block.should raise_error(Money::UnknownRate)
|
44
|
-
end
|
45
|
-
|
46
|
-
describe '.exchange' do
|
47
|
-
context 'sterling to euros using a rate of 1.39' do
|
48
|
-
it 'returns the correct amount' do
|
49
|
-
@bank.add_rate('GBP', 'EUR', 1.38)
|
50
|
-
@bank.exchange(10000, 'GBP', 'EUR').should == 13800
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
context 'dollars to euros using a rate of 0.86' do
|
55
|
-
it 'returns the correct amount' do
|
56
|
-
@bank.add_rate('USD', 'EUR', 0.86)
|
57
|
-
@bank.exchange(10000, 'USD', 'EUR').should == 8600
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
context 'TND to USD using a rate of 0.67138' do
|
62
|
-
it 'returns the correct amount' do
|
63
|
-
@bank.add_rate('TND', 'USD', 0.67138)
|
64
|
-
@bank.exchange(1000, 'TND', 'USD').should == 67
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
context 'USD to TND using a rate of 1.32862' do
|
69
|
-
it 'returns the correct amount' do
|
70
|
-
@bank.add_rate('USD', 'TND', 1.32862)
|
71
|
-
@bank.exchange(1000, 'USD', 'TND').should == 13286
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
context 'using custom rounding methods' do
|
77
|
-
describe 'passing a rounding method to #new' do
|
78
|
-
before :each do
|
79
|
-
mth = Proc.new{|ex| ex.ceil }
|
80
|
-
@bank = Money::VariableExchangeBank.new(&mth)
|
81
|
-
end
|
82
|
-
|
83
|
-
it 'should use @rounding_method' do
|
84
|
-
@bank.add_rate('USD', 'EUR', 0.86)
|
85
|
-
@bank.exchange(10, 'USD', 'EUR').should == 9
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
describe 'passing a rounding method to #exchange' do
|
90
|
-
it 'should use &block' do
|
91
|
-
@bank.add_rate('USD', 'EUR', 0.86)
|
92
|
-
@bank.exchange(10, 'USD', 'EUR').should == 8
|
93
|
-
|
94
|
-
mth = Proc.new{|ex| ex.ceil }
|
95
|
-
@bank.exchange(10, 'USD', 'EUR', &mth).should == 9
|
96
|
-
|
97
|
-
@bank.exchange(10, 'USD', 'EUR'){|ex| ex.ceil }.should == 9
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|