money 3.6.1 → 3.6.2

Sign up to get free protection for your applications and to get access to all the features.
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'
@@ -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 [Money] from The +Money+ object to exchange.
60
- # @param [Currency, String, Symbol] to_currency The currency to exchange
61
- # to.
62
- #
63
- # @yield [n] Optional block to use when rounding after exchanging one
64
- # currency for another.
65
- # @yieldparam [Float] n The resulting float after exchanging one currency
66
- # for another.
67
- # @yieldreturn [Integer]
68
- #
69
- # @return [Money]
70
- #
71
- # @raise +Money::Bank::UnknownRate+ if the conversion rate is unknown.
72
- #
73
- # @example
74
- # bank = Money::Bank::VariableExchange.new
75
- # bank.add_rate("USD", "CAD", 1.24515)
76
- # bank.add_rate("CAD", "USD", 0.803115)
77
- #
78
- # c1 = 100_00.to_money("USD")
79
- # c2 = 100_00.to_money("CAD")
80
- #
81
- # # Exchange 100 USD to CAD:
82
- # bank.exchange_with(c1, "CAD") #=> #<Money @cents=1245150>
83
- #
84
- # # Exchange 100 CAD to USD:
85
- # bank.exchange_with(c2, "USD") #=> #<Money @cents=803115>
86
- def exchange_with(from, to_currency, &block)
87
- return from if same_currency?(from.currency, to_currency)
88
-
89
- rate = get_rate(from.currency, to_currency)
90
- unless rate
91
- raise UnknownRate, "No conversion rate known for '#{from.currency.iso_code}' -> '#{to_currency}'"
92
- end
93
- _to_currency_ = Currency.wrap(to_currency)
94
-
95
- 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))
96
-
97
- ex = cents * BigDecimal.new(rate.to_s)
98
- ex = ex.to_f
99
- ex = if block_given?
100
- block.call(ex)
101
- elsif @rounding_method
102
- @rounding_method.call(ex)
103
- else
104
- ex.to_s.to_i
105
- end
106
- Money.new(ex, _to_currency_)
107
- end
108
-
109
- # Registers a conversion rate and returns it (uses +#set_rate+).
110
- #
111
- # @param [Currency, String, Symbol] from Currency to exchange from.
112
- # @param [Currency, String, Symbol] to Currency to exchange to.
113
- # @param [Numeric] rate Rate to use when exchanging currencies.
114
- #
115
- # @return [Numeric]
116
- #
117
- # @example
118
- # bank = Money::Bank::VariableExchange.new
119
- # bank.add_rate("USD", "CAD", 1.24515)
120
- # bank.add_rate("CAD", "USD", 0.803115)
121
- def add_rate(from, to, rate)
122
- set_rate(from, to, rate)
123
- end
124
-
125
- # Set the rate for the given currencies. Uses +Mutex+ to synchronize data
126
- # access.
127
- #
128
- # @param [Currency, String, Symbol] from Currency to exchange from.
129
- # @param [Currency, String, Symbol] to Currency to exchange to.
130
- # @param [Numeric] rate Rate to use when exchanging currencies.
131
- #
132
- # @return [Numeric]
133
- #
134
- # @example
135
- # bank = Money::Bank::VariableExchange.new
136
- # bank.set_rate("USD", "CAD", 1.24515)
137
- # bank.set_rate("CAD", "USD", 0.803115)
138
- def set_rate(from, to, rate)
139
- @mutex.synchronize { @rates[rate_key_for(from, to)] = rate }
140
- end
141
-
142
- # Retrieve the rate for the given currencies. Uses +Mutex+ to synchronize
143
- # data access.
144
- #
145
- # @param [Currency, String, Symbol] from Currency to exchange from.
146
- # @param [Currency, String, Symbol] to Currency to exchange to.
147
- #
148
- # @return [Numeric]
149
- #
150
- # @example
151
- # bank = Money::Bank::VariableExchange.new
152
- # bank.set_rate("USD", "CAD", 1.24515)
153
- # bank.set_rate("CAD", "USD", 0.803115)
154
- #
155
- # bank.get_rate("USD", "CAD") #=> 1.24515
156
- # bank.get_rate("CAD", "USD") #=> 0.803115
157
- def get_rate(from, to)
158
- @mutex.synchronize { @rates[rate_key_for(from, to)] }
159
- end
160
-
161
- # Return the known rates as a string in the format specified. If +file+
162
- # is given will also write the string out to the file specified.
163
- # Available formats are +:json+, +:ruby+ and +:yaml+.
164
- #
165
- # @param [Symbol] format Request format for the resulting string.
166
- # @param [String] file Optional file location to write the rates to.
167
- #
168
- # @return [String]
169
- #
170
- # @raise +Money::Bank::UnknownRateFormat+ if format is unknown.
171
- #
172
- # @example
173
- # bank = Money::Bank::VariableExchange.new
174
- # bank.set_rate("USD", "CAD", 1.24515)
175
- # bank.set_rate("CAD", "USD", 0.803115)
176
- #
177
- # s = bank.export_rates(:json)
178
- # s #=> "{\"USD_TO_CAD\":1.24515,\"CAD_TO_USD\":0.803115}"
179
- def export_rates(format, file=nil)
180
- raise Money::Bank::UnknownRateFormat unless
181
- RATE_FORMATS.include? format
182
-
183
- s = ""
184
- @mutex.synchronize {
185
- s = case format
186
- when :json
187
- JSON.dump(@rates)
188
- when :ruby
189
- Marshal.dump(@rates)
190
- when :yaml
191
- YAML.dump(@rates)
192
- end
193
-
194
- unless file.nil?
195
- File.open(file, "w").write(s)
196
- end
197
- }
198
- s
199
- end
200
-
201
- # Loads rates provided in +s+ given the specified format. Available
202
- # formats are +:json+, +:ruby+ and +:yaml+.
203
- #
204
- # @param [Symbol] format The format of +s+.
205
- # @param [String] s The rates string.
206
- #
207
- # @return [self]
208
- #
209
- # @raise +Money::Bank::UnknownRateFormat+ if format is unknown.
210
- #
211
- # @example
212
- # s = "{\"USD_TO_CAD\":1.24515,\"CAD_TO_USD\":0.803115}"
213
- # bank = Money::Bank::VariableExchange.new
214
- # bank.import_rates(:json, s)
215
- #
216
- # bank.get_rate("USD", "CAD") #=> 1.24515
217
- # bank.get_rate("CAD", "USD") #=> 0.803115
218
- def import_rates(format, s)
219
- raise Money::Bank::UnknownRateFormat unless
220
- RATE_FORMATS.include? format
221
-
222
- @mutex.synchronize {
223
- @rates = case format
224
- when :json
225
- JSON.load(s)
226
- when :ruby
227
- Marshal.load(s)
228
- when :yaml
229
- YAML.load(s)
230
- end
231
- }
232
- self
233
- end
234
-
235
- private
236
-
237
- # Return the rate hashkey for the given currencies.
238
- #
239
- # @param [Currency, String, Symbol] from The currency to exchange from.
240
- # @param [Currency, String, Symbol] to The currency to exchange to.
241
- #
242
- # @return [String]
243
- #
244
- # @example
245
- # rate_key_for("USD", "CAD") #=> "USD_TO_CAD"
246
- def rate_key_for(from, to)
247
- "#{Currency.wrap(from).iso_code}_TO_#{Currency.wrap(to).iso_code}".upcase
248
- end
249
- end
250
- end
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