money 3.6.1 → 3.6.2
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.md +335 -319
- data/LICENSE +21 -21
- data/README.md +214 -209
- data/Rakefile +49 -49
- data/lib/money.rb +27 -27
- data/lib/money/bank/base.rb +131 -131
- data/lib/money/bank/variable_exchange.rb +252 -251
- data/lib/money/core_extensions.rb +82 -63
- data/lib/money/currency.rb +422 -415
- data/lib/money/money.rb +387 -1210
- data/lib/money/money/arithmetic.rb +246 -0
- data/lib/money/money/formatting.rb +234 -0
- data/lib/money/money/parsing.rb +350 -0
- data/money.gemspec +34 -27
- data/spec/bank/base_spec.rb +72 -72
- data/spec/bank/variable_exchange_spec.rb +238 -238
- data/spec/core_extensions_spec.rb +158 -142
- data/spec/currency_spec.rb +133 -128
- data/spec/money/arithmetic_spec.rb +479 -0
- data/spec/money/formatting_spec.rb +352 -0
- data/spec/money/parsing_spec.rb +197 -0
- data/spec/money_spec.rb +271 -1268
- data/spec/spec_helper.rb +28 -17
- metadata +33 -23
- data/lib/money.rbc +0 -170
- data/lib/money/bank/base.rbc +0 -800
- data/lib/money/bank/variable_exchange.rbc +0 -2496
- data/lib/money/core_extensions.rbc +0 -474
- data/lib/money/currency.rbc +0 -22600
- data/lib/money/money.rbc +0 -10070
- data/spec/bank/base_spec.rbc +0 -2409
- data/spec/bank/variable_exchange_spec.rbc +0 -7389
- data/spec/core_extensions_spec.rbc +0 -5215
- data/spec/currency_spec.rbc +0 -4341
- data/spec/money_spec.rbc +0 -50121
- data/spec/spec_helper.rbc +0 -346
data/Rakefile
CHANGED
@@ -1,49 +1,49 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rake/clean'
|
3
|
-
|
4
|
-
CLOBBER.include('doc', '.yardoc')
|
5
|
-
|
6
|
-
def gemspec
|
7
|
-
@gemspec ||= begin
|
8
|
-
file = File.expand_path("../money.gemspec", __FILE__)
|
9
|
-
eval(File.read(file), binding, file)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
begin
|
14
|
-
require 'rspec/core/rake_task'
|
15
|
-
RSpec::Core::RakeTask.new
|
16
|
-
rescue LoadError
|
17
|
-
task(:spec){abort "`gem install rspec` to run specs"}
|
18
|
-
end
|
19
|
-
task :default => :spec
|
20
|
-
task :test => :spec
|
21
|
-
|
22
|
-
begin
|
23
|
-
require 'yard'
|
24
|
-
YARD::Rake::YardocTask.new do |t|
|
25
|
-
t.options << "--files" << "CHANGELOG.md,LICENSE"
|
26
|
-
end
|
27
|
-
rescue LoadError
|
28
|
-
task(:yardoc){abort "`gem install yard` to generate documentation"}
|
29
|
-
end
|
30
|
-
|
31
|
-
begin
|
32
|
-
require 'rake/gempackagetask'
|
33
|
-
Rake::GemPackageTask.new(gemspec) do |pkg|
|
34
|
-
pkg.gem_spec = gemspec
|
35
|
-
end
|
36
|
-
task :gem => :gemspec
|
37
|
-
rescue LoadError
|
38
|
-
task(:gem){abort "`gem install rake` to package gems"}
|
39
|
-
end
|
40
|
-
|
41
|
-
desc "Install the gem locally"
|
42
|
-
task :install => :gem do
|
43
|
-
sh "gem install pkg/#{gemspec.full_name}.gem"
|
44
|
-
end
|
45
|
-
|
46
|
-
desc "Validate the gemspec"
|
47
|
-
task :gemspec do
|
48
|
-
gemspec.validate
|
49
|
-
end
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/clean'
|
3
|
+
|
4
|
+
CLOBBER.include('doc', '.yardoc')
|
5
|
+
|
6
|
+
def gemspec
|
7
|
+
@gemspec ||= begin
|
8
|
+
file = File.expand_path("../money.gemspec", __FILE__)
|
9
|
+
eval(File.read(file), binding, file)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
begin
|
14
|
+
require 'rspec/core/rake_task'
|
15
|
+
RSpec::Core::RakeTask.new
|
16
|
+
rescue LoadError
|
17
|
+
task(:spec){abort "`gem install rspec` to run specs"}
|
18
|
+
end
|
19
|
+
task :default => :spec
|
20
|
+
task :test => :spec
|
21
|
+
|
22
|
+
begin
|
23
|
+
require 'yard'
|
24
|
+
YARD::Rake::YardocTask.new do |t|
|
25
|
+
t.options << "--files" << "CHANGELOG.md,LICENSE"
|
26
|
+
end
|
27
|
+
rescue LoadError
|
28
|
+
task(:yardoc){abort "`gem install yard` to generate documentation"}
|
29
|
+
end
|
30
|
+
|
31
|
+
begin
|
32
|
+
require 'rake/gempackagetask'
|
33
|
+
Rake::GemPackageTask.new(gemspec) do |pkg|
|
34
|
+
pkg.gem_spec = gemspec
|
35
|
+
end
|
36
|
+
task :gem => :gemspec
|
37
|
+
rescue LoadError
|
38
|
+
task(:gem){abort "`gem install rake` to package gems"}
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "Install the gem locally"
|
42
|
+
task :install => :gem do
|
43
|
+
sh "gem install pkg/#{gemspec.full_name}.gem"
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "Validate the gemspec"
|
47
|
+
task :gemspec do
|
48
|
+
gemspec.validate
|
49
|
+
end
|
data/lib/money.rb
CHANGED
@@ -1,27 +1,27 @@
|
|
1
|
-
# Copyright (c) 2005 Tobias Luetke
|
2
|
-
# Copyright (c) 2008 Phusion
|
3
|
-
#
|
4
|
-
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
-
# a copy of this software and associated documentation files (the
|
6
|
-
# "Software"), to deal in the Software without restriction, including
|
7
|
-
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
-
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
-
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
-
# the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be
|
13
|
-
# included in all copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
-
|
23
|
-
require 'bigdecimal'
|
24
|
-
require 'i18n' rescue LoadError
|
25
|
-
require 'money/currency'
|
26
|
-
require 'money/money'
|
27
|
-
require 'money/core_extensions'
|
1
|
+
# Copyright (c) 2005 Tobias Luetke
|
2
|
+
# Copyright (c) 2008 Phusion
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'bigdecimal'
|
24
|
+
require 'i18n' rescue LoadError
|
25
|
+
require 'money/currency'
|
26
|
+
require 'money/money'
|
27
|
+
require 'money/core_extensions'
|
data/lib/money/bank/base.rb
CHANGED
@@ -1,131 +1,131 @@
|
|
1
|
-
require 'thread'
|
2
|
-
|
3
|
-
class Money
|
4
|
-
# Provides classes that aid in the ability of exchange one currency with
|
5
|
-
# another.
|
6
|
-
module Bank
|
7
|
-
|
8
|
-
# The lowest Money::Bank error class.
|
9
|
-
# All Money::Bank errors should inherit from it.
|
10
|
-
class Error < StandardError
|
11
|
-
end
|
12
|
-
|
13
|
-
# Raised when the bank doesn't know about the conversion rate
|
14
|
-
# for specified currencies.
|
15
|
-
class UnknownRate < Error
|
16
|
-
end
|
17
|
-
|
18
|
-
|
19
|
-
# Money::Bank::Base is the basic interface for creating a money exchange
|
20
|
-
# object, also called Bank.
|
21
|
-
#
|
22
|
-
# A Bank is responsible for storing exchange rates, take a Money object as
|
23
|
-
# input and returns the corresponding Money object converted into an other
|
24
|
-
# currency.
|
25
|
-
#
|
26
|
-
# This class exists for aiding in the creating of other classes to exchange
|
27
|
-
# money between different currencies. When creating a subclass you will
|
28
|
-
# need to implement the following methods to exchange money between
|
29
|
-
# currencies:
|
30
|
-
#
|
31
|
-
# - #exchange_with(Money) #=> Money
|
32
|
-
#
|
33
|
-
# See Money::Bank::VariableExchange for a real example.
|
34
|
-
#
|
35
|
-
# Also, you can extend +Money::Bank::VariableExchange+ instead of
|
36
|
-
# +Money::Bank::Base+ if your bank implementation needs to store rates
|
37
|
-
# internally.
|
38
|
-
#
|
39
|
-
# @abstract Subclass and override +#exchange_with+ to implement a custom
|
40
|
-
# +Money::Bank+ class. You can also override +#setup+ instead of
|
41
|
-
# +#initialize+ to setup initial variables, etc.
|
42
|
-
class Base
|
43
|
-
|
44
|
-
# Returns the singleton instance of the Base bank.
|
45
|
-
#
|
46
|
-
# @return [Money::Bank::Base]
|
47
|
-
def self.instance
|
48
|
-
@@singleton ||= self.new
|
49
|
-
end
|
50
|
-
|
51
|
-
# The rounding method to use when exchanging rates.
|
52
|
-
#
|
53
|
-
# @return [Proc]
|
54
|
-
attr_reader :rounding_method
|
55
|
-
|
56
|
-
# Initializes a new +Money::Bank::Base+ object. An optional block can be
|
57
|
-
# passed to dictate the rounding method that +#exchange_with+ can use.
|
58
|
-
#
|
59
|
-
# @yield [n] Optional block to use when rounding after exchanging one
|
60
|
-
# currency for another.
|
61
|
-
# @yieldparam [Float] n The resulting float after exchanging one currency
|
62
|
-
# for another.
|
63
|
-
# @yieldreturn [Integer]
|
64
|
-
#
|
65
|
-
# @return [Money::Bank::Base]
|
66
|
-
#
|
67
|
-
# @example
|
68
|
-
# Money::Bank::Base.new #=> #<Money::Bank::Base @rounding_method=nil>
|
69
|
-
# Money::Bank::Base.new {|n|
|
70
|
-
# n.floor
|
71
|
-
# } #=> #<Money::Bank::Base @round_method=#<Proc>>
|
72
|
-
def initialize(&block)
|
73
|
-
@rounding_method = block
|
74
|
-
setup
|
75
|
-
end
|
76
|
-
|
77
|
-
# Called after initialize. Subclasses can use this method to setup
|
78
|
-
# variables, etc that they normally would in +#initialize+.
|
79
|
-
#
|
80
|
-
# @abstract Subclass and override +#setup+ to implement a custom
|
81
|
-
# +Money::Bank+ class.
|
82
|
-
#
|
83
|
-
# @return [self]
|
84
|
-
def setup
|
85
|
-
end
|
86
|
-
|
87
|
-
# Exchanges the given +Money+ object to a new +Money+ object in
|
88
|
-
# +to_currency+.
|
89
|
-
#
|
90
|
-
# @abstract Subclass and override +#exchange_with+ to implement a custom
|
91
|
-
# +Money::Bank+ class.
|
92
|
-
#
|
93
|
-
# @raise NotImplementedError
|
94
|
-
#
|
95
|
-
# @param [Money] from The +Money+ object to exchange from.
|
96
|
-
# @param [Money::Currency, String, Symbol] to_currency The currency
|
97
|
-
# string or object to exchange to.
|
98
|
-
# @yield [n] Optional block to use to round the result after making
|
99
|
-
# the exchange.
|
100
|
-
# @yieldparam [Float] n The result after exchanging from one currency to
|
101
|
-
# the other.
|
102
|
-
# @yieldreturn [Integer]
|
103
|
-
#
|
104
|
-
# @return [Money]
|
105
|
-
def exchange_with(from, to_currency, &block)
|
106
|
-
raise NotImplementedError, "#exchange_with must be implemented"
|
107
|
-
end
|
108
|
-
|
109
|
-
|
110
|
-
# Given two currency strings or object, checks whether they're both the
|
111
|
-
# same currency. Return +true+ if the currencies are the same, +false+
|
112
|
-
# otherwise.
|
113
|
-
#
|
114
|
-
# @param [Money::Currency, String, Symbol] currency1 The first currency
|
115
|
-
# to compare.
|
116
|
-
# @param [Money::Currency, String, Symbol] currency2 The second currency
|
117
|
-
# to compare.
|
118
|
-
#
|
119
|
-
# @return [Boolean]
|
120
|
-
#
|
121
|
-
# @example
|
122
|
-
# same_currency?("usd", "USD") #=> true
|
123
|
-
# same_currency?("usd", "EUR") #=> false
|
124
|
-
# same_currency?("usd", Currency.new("USD") #=> true
|
125
|
-
# same_currency?("usd", "USD") #=> true
|
126
|
-
def same_currency?(currency1, currency2)
|
127
|
-
Currency.wrap(currency1) == Currency.wrap(currency2)
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
class Money
|
4
|
+
# Provides classes that aid in the ability of exchange one currency with
|
5
|
+
# another.
|
6
|
+
module Bank
|
7
|
+
|
8
|
+
# The lowest Money::Bank error class.
|
9
|
+
# All Money::Bank errors should inherit from it.
|
10
|
+
class Error < StandardError
|
11
|
+
end
|
12
|
+
|
13
|
+
# Raised when the bank doesn't know about the conversion rate
|
14
|
+
# for specified currencies.
|
15
|
+
class UnknownRate < Error
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
# Money::Bank::Base is the basic interface for creating a money exchange
|
20
|
+
# object, also called Bank.
|
21
|
+
#
|
22
|
+
# A Bank is responsible for storing exchange rates, take a Money object as
|
23
|
+
# input and returns the corresponding Money object converted into an other
|
24
|
+
# currency.
|
25
|
+
#
|
26
|
+
# This class exists for aiding in the creating of other classes to exchange
|
27
|
+
# money between different currencies. When creating a subclass you will
|
28
|
+
# need to implement the following methods to exchange money between
|
29
|
+
# currencies:
|
30
|
+
#
|
31
|
+
# - #exchange_with(Money) #=> Money
|
32
|
+
#
|
33
|
+
# See Money::Bank::VariableExchange for a real example.
|
34
|
+
#
|
35
|
+
# Also, you can extend +Money::Bank::VariableExchange+ instead of
|
36
|
+
# +Money::Bank::Base+ if your bank implementation needs to store rates
|
37
|
+
# internally.
|
38
|
+
#
|
39
|
+
# @abstract Subclass and override +#exchange_with+ to implement a custom
|
40
|
+
# +Money::Bank+ class. You can also override +#setup+ instead of
|
41
|
+
# +#initialize+ to setup initial variables, etc.
|
42
|
+
class Base
|
43
|
+
|
44
|
+
# Returns the singleton instance of the Base bank.
|
45
|
+
#
|
46
|
+
# @return [Money::Bank::Base]
|
47
|
+
def self.instance
|
48
|
+
@@singleton ||= self.new
|
49
|
+
end
|
50
|
+
|
51
|
+
# The rounding method to use when exchanging rates.
|
52
|
+
#
|
53
|
+
# @return [Proc]
|
54
|
+
attr_reader :rounding_method
|
55
|
+
|
56
|
+
# Initializes a new +Money::Bank::Base+ object. An optional block can be
|
57
|
+
# passed to dictate the rounding method that +#exchange_with+ can use.
|
58
|
+
#
|
59
|
+
# @yield [n] Optional block to use when rounding after exchanging one
|
60
|
+
# currency for another.
|
61
|
+
# @yieldparam [Float] n The resulting float after exchanging one currency
|
62
|
+
# for another.
|
63
|
+
# @yieldreturn [Integer]
|
64
|
+
#
|
65
|
+
# @return [Money::Bank::Base]
|
66
|
+
#
|
67
|
+
# @example
|
68
|
+
# Money::Bank::Base.new #=> #<Money::Bank::Base @rounding_method=nil>
|
69
|
+
# Money::Bank::Base.new {|n|
|
70
|
+
# n.floor
|
71
|
+
# } #=> #<Money::Bank::Base @round_method=#<Proc>>
|
72
|
+
def initialize(&block)
|
73
|
+
@rounding_method = block
|
74
|
+
setup
|
75
|
+
end
|
76
|
+
|
77
|
+
# Called after initialize. Subclasses can use this method to setup
|
78
|
+
# variables, etc that they normally would in +#initialize+.
|
79
|
+
#
|
80
|
+
# @abstract Subclass and override +#setup+ to implement a custom
|
81
|
+
# +Money::Bank+ class.
|
82
|
+
#
|
83
|
+
# @return [self]
|
84
|
+
def setup
|
85
|
+
end
|
86
|
+
|
87
|
+
# Exchanges the given +Money+ object to a new +Money+ object in
|
88
|
+
# +to_currency+.
|
89
|
+
#
|
90
|
+
# @abstract Subclass and override +#exchange_with+ to implement a custom
|
91
|
+
# +Money::Bank+ class.
|
92
|
+
#
|
93
|
+
# @raise NotImplementedError
|
94
|
+
#
|
95
|
+
# @param [Money] from The +Money+ object to exchange from.
|
96
|
+
# @param [Money::Currency, String, Symbol] to_currency The currency
|
97
|
+
# string or object to exchange to.
|
98
|
+
# @yield [n] Optional block to use to round the result after making
|
99
|
+
# the exchange.
|
100
|
+
# @yieldparam [Float] n The result after exchanging from one currency to
|
101
|
+
# the other.
|
102
|
+
# @yieldreturn [Integer]
|
103
|
+
#
|
104
|
+
# @return [Money]
|
105
|
+
def exchange_with(from, to_currency, &block)
|
106
|
+
raise NotImplementedError, "#exchange_with must be implemented"
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
# Given two currency strings or object, checks whether they're both the
|
111
|
+
# same currency. Return +true+ if the currencies are the same, +false+
|
112
|
+
# otherwise.
|
113
|
+
#
|
114
|
+
# @param [Money::Currency, String, Symbol] currency1 The first currency
|
115
|
+
# to compare.
|
116
|
+
# @param [Money::Currency, String, Symbol] currency2 The second currency
|
117
|
+
# to compare.
|
118
|
+
#
|
119
|
+
# @return [Boolean]
|
120
|
+
#
|
121
|
+
# @example
|
122
|
+
# same_currency?("usd", "USD") #=> true
|
123
|
+
# same_currency?("usd", "EUR") #=> false
|
124
|
+
# same_currency?("usd", Currency.new("USD") #=> true
|
125
|
+
# same_currency?("usd", "USD") #=> true
|
126
|
+
def same_currency?(currency1, currency2)
|
127
|
+
Currency.wrap(currency1) == Currency.wrap(currency2)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -1,251 +1,252 @@
|
|
1
|
-
require 'money/bank/base'
|
2
|
-
autoload :JSON, 'json'
|
3
|
-
autoload :YAML, 'yaml'
|
4
|
-
|
5
|
-
class Money
|
6
|
-
module Bank
|
7
|
-
# Thrown when an unknown rate format is requested.
|
8
|
-
class UnknownRateFormat < StandardError; end
|
9
|
-
|
10
|
-
# Class for aiding in exchanging money between different currencies. By
|
11
|
-
# default, the +Money+ class uses an object of this class (accessible
|
12
|
-
# through +Money#bank+) for performing currency exchanges.
|
13
|
-
#
|
14
|
-
# By default, +Money::Bank::VariableExchange+ has no knowledge about
|
15
|
-
# conversion rates. One must manually specify them with +add_rate+, after
|
16
|
-
# which one can perform exchanges with +#exchange_with+.
|
17
|
-
#
|
18
|
-
# @example
|
19
|
-
# bank = Money::Bank::VariableExchange.new
|
20
|
-
# bank.add_rate("USD", "CAD", 1.24515)
|
21
|
-
# bank.add_rate("CAD", "USD", 0.803115)
|
22
|
-
#
|
23
|
-
# c1 = 100_00.to_money("USD")
|
24
|
-
# c2 = 100_00.to_money("CAD")
|
25
|
-
#
|
26
|
-
# # Exchange 100 USD to CAD:
|
27
|
-
# bank.exchange_with(c1, "CAD") #=> #<Money @cents=1245150>
|
28
|
-
#
|
29
|
-
# # Exchange 100 CAD to USD:
|
30
|
-
# bank.exchange_with(c2, "USD") #=> #<Money @cents=803115>
|
31
|
-
class VariableExchange < Base
|
32
|
-
|
33
|
-
attr_reader :rates
|
34
|
-
|
35
|
-
# Available formats for importing/exporting rates.
|
36
|
-
RATE_FORMATS = [:json, :ruby, :yaml]
|
37
|
-
|
38
|
-
# Setup rates hash and mutex for rates locking
|
39
|
-
#
|
40
|
-
# @return [self]
|
41
|
-
def setup
|
42
|
-
@rates = {}
|
43
|
-
@mutex = Mutex.new
|
44
|
-
self
|
45
|
-
end
|
46
|
-
|
47
|
-
def marshal_dump
|
48
|
-
[@rates, @rounding_method]
|
49
|
-
end
|
50
|
-
|
51
|
-
def marshal_load(arr)
|
52
|
-
@rates, @rounding_method = arr
|
53
|
-
@mutex = Mutex.new
|
54
|
-
end
|
55
|
-
|
56
|
-
# Exchanges the given +Money+ object to a new +Money+ object in
|
57
|
-
# +to_currency+.
|
58
|
-
#
|
59
|
-
# @param
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
# bank
|
76
|
-
# bank.add_rate("
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
ex =
|
99
|
-
ex =
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
#
|
111
|
-
#
|
112
|
-
# @param [Currency, String, Symbol]
|
113
|
-
# @param [
|
114
|
-
#
|
115
|
-
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
#
|
119
|
-
# bank
|
120
|
-
# bank.add_rate("
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
#
|
127
|
-
#
|
128
|
-
#
|
129
|
-
# @param [Currency, String, Symbol]
|
130
|
-
# @param [
|
131
|
-
#
|
132
|
-
#
|
133
|
-
#
|
134
|
-
#
|
135
|
-
#
|
136
|
-
# bank
|
137
|
-
# bank.set_rate("
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
# @param [Currency, String, Symbol]
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
#
|
151
|
-
#
|
152
|
-
# bank
|
153
|
-
# bank.set_rate("
|
154
|
-
#
|
155
|
-
#
|
156
|
-
# bank.get_rate("
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
#
|
163
|
-
#
|
164
|
-
#
|
165
|
-
#
|
166
|
-
# @param [
|
167
|
-
#
|
168
|
-
#
|
169
|
-
#
|
170
|
-
#
|
171
|
-
#
|
172
|
-
#
|
173
|
-
#
|
174
|
-
# bank
|
175
|
-
# bank.set_rate("
|
176
|
-
#
|
177
|
-
#
|
178
|
-
# s
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
#
|
203
|
-
#
|
204
|
-
#
|
205
|
-
# @param [
|
206
|
-
#
|
207
|
-
#
|
208
|
-
#
|
209
|
-
#
|
210
|
-
#
|
211
|
-
#
|
212
|
-
#
|
213
|
-
#
|
214
|
-
# bank.
|
215
|
-
#
|
216
|
-
#
|
217
|
-
# bank.get_rate("
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
#
|
239
|
-
#
|
240
|
-
# @param [Currency, String, Symbol]
|
241
|
-
#
|
242
|
-
#
|
243
|
-
#
|
244
|
-
#
|
245
|
-
#
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
end
|
1
|
+
require 'money/bank/base'
|
2
|
+
autoload :JSON, 'json'
|
3
|
+
autoload :YAML, 'yaml'
|
4
|
+
|
5
|
+
class Money
|
6
|
+
module Bank
|
7
|
+
# Thrown when an unknown rate format is requested.
|
8
|
+
class UnknownRateFormat < StandardError; end
|
9
|
+
|
10
|
+
# Class for aiding in exchanging money between different currencies. By
|
11
|
+
# default, the +Money+ class uses an object of this class (accessible
|
12
|
+
# through +Money#bank+) for performing currency exchanges.
|
13
|
+
#
|
14
|
+
# By default, +Money::Bank::VariableExchange+ has no knowledge about
|
15
|
+
# conversion rates. One must manually specify them with +add_rate+, after
|
16
|
+
# which one can perform exchanges with +#exchange_with+.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# bank = Money::Bank::VariableExchange.new
|
20
|
+
# bank.add_rate("USD", "CAD", 1.24515)
|
21
|
+
# bank.add_rate("CAD", "USD", 0.803115)
|
22
|
+
#
|
23
|
+
# c1 = 100_00.to_money("USD")
|
24
|
+
# c2 = 100_00.to_money("CAD")
|
25
|
+
#
|
26
|
+
# # Exchange 100 USD to CAD:
|
27
|
+
# bank.exchange_with(c1, "CAD") #=> #<Money @cents=1245150>
|
28
|
+
#
|
29
|
+
# # Exchange 100 CAD to USD:
|
30
|
+
# bank.exchange_with(c2, "USD") #=> #<Money @cents=803115>
|
31
|
+
class VariableExchange < Base
|
32
|
+
|
33
|
+
attr_reader :rates
|
34
|
+
|
35
|
+
# Available formats for importing/exporting rates.
|
36
|
+
RATE_FORMATS = [:json, :ruby, :yaml]
|
37
|
+
|
38
|
+
# Setup rates hash and mutex for rates locking
|
39
|
+
#
|
40
|
+
# @return [self]
|
41
|
+
def setup
|
42
|
+
@rates = {}
|
43
|
+
@mutex = Mutex.new
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def marshal_dump
|
48
|
+
[@rates, @rounding_method]
|
49
|
+
end
|
50
|
+
|
51
|
+
def marshal_load(arr)
|
52
|
+
@rates, @rounding_method = arr
|
53
|
+
@mutex = Mutex.new
|
54
|
+
end
|
55
|
+
|
56
|
+
# Exchanges the given +Money+ object to a new +Money+ object in
|
57
|
+
# +to_currency+.
|
58
|
+
#
|
59
|
+
# @param [Money] from
|
60
|
+
# The +Money+ object to exchange.
|
61
|
+
# @param [Currency, String, Symbol] to_currency
|
62
|
+
# The currency to exchange to.
|
63
|
+
#
|
64
|
+
# @yield [n] Optional block to use when rounding after exchanging one
|
65
|
+
# currency for another.
|
66
|
+
# @yieldparam [Float] n The resulting float after exchanging one currency
|
67
|
+
# for another.
|
68
|
+
# @yieldreturn [Integer]
|
69
|
+
#
|
70
|
+
# @return [Money]
|
71
|
+
#
|
72
|
+
# @raise +Money::Bank::UnknownRate+ if the conversion rate is unknown.
|
73
|
+
#
|
74
|
+
# @example
|
75
|
+
# bank = Money::Bank::VariableExchange.new
|
76
|
+
# bank.add_rate("USD", "CAD", 1.24515)
|
77
|
+
# bank.add_rate("CAD", "USD", 0.803115)
|
78
|
+
#
|
79
|
+
# c1 = 100_00.to_money("USD")
|
80
|
+
# c2 = 100_00.to_money("CAD")
|
81
|
+
#
|
82
|
+
# # Exchange 100 USD to CAD:
|
83
|
+
# bank.exchange_with(c1, "CAD") #=> #<Money @cents=1245150>
|
84
|
+
#
|
85
|
+
# # Exchange 100 CAD to USD:
|
86
|
+
# bank.exchange_with(c2, "USD") #=> #<Money @cents=803115>
|
87
|
+
def exchange_with(from, to_currency)
|
88
|
+
return from if same_currency?(from.currency, to_currency)
|
89
|
+
|
90
|
+
rate = get_rate(from.currency, to_currency)
|
91
|
+
unless rate
|
92
|
+
raise UnknownRate, "No conversion rate known for '#{from.currency.iso_code}' -> '#{to_currency}'"
|
93
|
+
end
|
94
|
+
_to_currency_ = Currency.wrap(to_currency)
|
95
|
+
|
96
|
+
cents = BigDecimal.new(from.cents.to_s) / (BigDecimal.new(from.currency.subunit_to_unit.to_s) / BigDecimal.new(_to_currency_.subunit_to_unit.to_s))
|
97
|
+
|
98
|
+
ex = cents * BigDecimal.new(rate.to_s)
|
99
|
+
ex = ex.to_f
|
100
|
+
ex = if block_given?
|
101
|
+
yield ex
|
102
|
+
elsif @rounding_method
|
103
|
+
@rounding_method.call(ex)
|
104
|
+
else
|
105
|
+
ex.to_s.to_i
|
106
|
+
end
|
107
|
+
Money.new(ex, _to_currency_)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Registers a conversion rate and returns it (uses +#set_rate+).
|
111
|
+
#
|
112
|
+
# @param [Currency, String, Symbol] from Currency to exchange from.
|
113
|
+
# @param [Currency, String, Symbol] to Currency to exchange to.
|
114
|
+
# @param [Numeric] rate Rate to use when exchanging currencies.
|
115
|
+
#
|
116
|
+
# @return [Numeric]
|
117
|
+
#
|
118
|
+
# @example
|
119
|
+
# bank = Money::Bank::VariableExchange.new
|
120
|
+
# bank.add_rate("USD", "CAD", 1.24515)
|
121
|
+
# bank.add_rate("CAD", "USD", 0.803115)
|
122
|
+
def add_rate(from, to, rate)
|
123
|
+
set_rate(from, to, rate)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Set the rate for the given currencies. Uses +Mutex+ to synchronize data
|
127
|
+
# access.
|
128
|
+
#
|
129
|
+
# @param [Currency, String, Symbol] from Currency to exchange from.
|
130
|
+
# @param [Currency, String, Symbol] to Currency to exchange to.
|
131
|
+
# @param [Numeric] rate Rate to use when exchanging currencies.
|
132
|
+
#
|
133
|
+
# @return [Numeric]
|
134
|
+
#
|
135
|
+
# @example
|
136
|
+
# bank = Money::Bank::VariableExchange.new
|
137
|
+
# bank.set_rate("USD", "CAD", 1.24515)
|
138
|
+
# bank.set_rate("CAD", "USD", 0.803115)
|
139
|
+
def set_rate(from, to, rate)
|
140
|
+
@mutex.synchronize { @rates[rate_key_for(from, to)] = rate }
|
141
|
+
end
|
142
|
+
|
143
|
+
# Retrieve the rate for the given currencies. Uses +Mutex+ to synchronize
|
144
|
+
# data access.
|
145
|
+
#
|
146
|
+
# @param [Currency, String, Symbol] from Currency to exchange from.
|
147
|
+
# @param [Currency, String, Symbol] to Currency to exchange to.
|
148
|
+
#
|
149
|
+
# @return [Numeric]
|
150
|
+
#
|
151
|
+
# @example
|
152
|
+
# bank = Money::Bank::VariableExchange.new
|
153
|
+
# bank.set_rate("USD", "CAD", 1.24515)
|
154
|
+
# bank.set_rate("CAD", "USD", 0.803115)
|
155
|
+
#
|
156
|
+
# bank.get_rate("USD", "CAD") #=> 1.24515
|
157
|
+
# bank.get_rate("CAD", "USD") #=> 0.803115
|
158
|
+
def get_rate(from, to)
|
159
|
+
@mutex.synchronize { @rates[rate_key_for(from, to)] }
|
160
|
+
end
|
161
|
+
|
162
|
+
# Return the known rates as a string in the format specified. If +file+
|
163
|
+
# is given will also write the string out to the file specified.
|
164
|
+
# Available formats are +:json+, +:ruby+ and +:yaml+.
|
165
|
+
#
|
166
|
+
# @param [Symbol] format Request format for the resulting string.
|
167
|
+
# @param [String] file Optional file location to write the rates to.
|
168
|
+
#
|
169
|
+
# @return [String]
|
170
|
+
#
|
171
|
+
# @raise +Money::Bank::UnknownRateFormat+ if format is unknown.
|
172
|
+
#
|
173
|
+
# @example
|
174
|
+
# bank = Money::Bank::VariableExchange.new
|
175
|
+
# bank.set_rate("USD", "CAD", 1.24515)
|
176
|
+
# bank.set_rate("CAD", "USD", 0.803115)
|
177
|
+
#
|
178
|
+
# s = bank.export_rates(:json)
|
179
|
+
# s #=> "{\"USD_TO_CAD\":1.24515,\"CAD_TO_USD\":0.803115}"
|
180
|
+
def export_rates(format, file=nil)
|
181
|
+
raise Money::Bank::UnknownRateFormat unless
|
182
|
+
RATE_FORMATS.include? format
|
183
|
+
|
184
|
+
s = ""
|
185
|
+
@mutex.synchronize {
|
186
|
+
s = case format
|
187
|
+
when :json
|
188
|
+
JSON.dump(@rates)
|
189
|
+
when :ruby
|
190
|
+
Marshal.dump(@rates)
|
191
|
+
when :yaml
|
192
|
+
YAML.dump(@rates)
|
193
|
+
end
|
194
|
+
|
195
|
+
unless file.nil?
|
196
|
+
File.open(file, "w").write(s)
|
197
|
+
end
|
198
|
+
}
|
199
|
+
s
|
200
|
+
end
|
201
|
+
|
202
|
+
# Loads rates provided in +s+ given the specified format. Available
|
203
|
+
# formats are +:json+, +:ruby+ and +:yaml+.
|
204
|
+
#
|
205
|
+
# @param [Symbol] format The format of +s+.
|
206
|
+
# @param [String] s The rates string.
|
207
|
+
#
|
208
|
+
# @return [self]
|
209
|
+
#
|
210
|
+
# @raise +Money::Bank::UnknownRateFormat+ if format is unknown.
|
211
|
+
#
|
212
|
+
# @example
|
213
|
+
# s = "{\"USD_TO_CAD\":1.24515,\"CAD_TO_USD\":0.803115}"
|
214
|
+
# bank = Money::Bank::VariableExchange.new
|
215
|
+
# bank.import_rates(:json, s)
|
216
|
+
#
|
217
|
+
# bank.get_rate("USD", "CAD") #=> 1.24515
|
218
|
+
# bank.get_rate("CAD", "USD") #=> 0.803115
|
219
|
+
def import_rates(format, s)
|
220
|
+
raise Money::Bank::UnknownRateFormat unless
|
221
|
+
RATE_FORMATS.include? format
|
222
|
+
|
223
|
+
@mutex.synchronize {
|
224
|
+
@rates = case format
|
225
|
+
when :json
|
226
|
+
JSON.load(s)
|
227
|
+
when :ruby
|
228
|
+
Marshal.load(s)
|
229
|
+
when :yaml
|
230
|
+
YAML.load(s)
|
231
|
+
end
|
232
|
+
}
|
233
|
+
self
|
234
|
+
end
|
235
|
+
|
236
|
+
private
|
237
|
+
|
238
|
+
# Return the rate hashkey for the given currencies.
|
239
|
+
#
|
240
|
+
# @param [Currency, String, Symbol] from The currency to exchange from.
|
241
|
+
# @param [Currency, String, Symbol] to The currency to exchange to.
|
242
|
+
#
|
243
|
+
# @return [String]
|
244
|
+
#
|
245
|
+
# @example
|
246
|
+
# rate_key_for("USD", "CAD") #=> "USD_TO_CAD"
|
247
|
+
def rate_key_for(from, to)
|
248
|
+
"#{Currency.wrap(from).iso_code}_TO_#{Currency.wrap(to).iso_code}".upcase
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|