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/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