money 4.0.1 → 4.0.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.
@@ -1,41 +1,41 @@
1
- {
2
- "eek": {
3
- "priority": 100,
4
- "iso_code": "EEK",
5
- "name": "Estonian Kroon",
6
- "symbol": "KR",
7
- "subunit": "Sent",
8
- "subunit_to_unit": 100,
9
- "symbol_first": false,
10
- "html_entity": "",
11
- "decimal_mark": ".",
12
- "thousands_separator": ",",
13
- "iso_numeric": "233"
14
- },
15
- "yen": {
16
- "priority": 100,
17
- "iso_code": "JPY",
18
- "name": "Japanese Yen",
19
- "symbol": "¥",
20
- "subunit": "Sen",
21
- "subunit_to_unit": 100,
22
- "symbol_first": true,
23
- "html_entity": "¥",
24
- "decimal_mark": ".",
25
- "thousands_separator": ",",
26
- "iso_numeric": ""
27
- },
28
- "ghc": {
29
- "priority": 100,
30
- "iso_code": "GHS",
31
- "name": "Ghanaian Cedi",
32
- "symbol": "₵",
33
- "subunit": "Pesewa",
34
- "subunit_to_unit": 100,
35
- "symbol_first": true,
36
- "html_entity": "₵",
37
- "decimal_mark": ".",
38
- "thousands_separator": ",",
39
- "iso_numeric": "288"
40
- }
41
- }
1
+ {
2
+ "eek": {
3
+ "priority": 100,
4
+ "iso_code": "EEK",
5
+ "name": "Estonian Kroon",
6
+ "symbol": "KR",
7
+ "subunit": "Sent",
8
+ "subunit_to_unit": 100,
9
+ "symbol_first": false,
10
+ "html_entity": "",
11
+ "decimal_mark": ".",
12
+ "thousands_separator": ",",
13
+ "iso_numeric": "233"
14
+ },
15
+ "yen": {
16
+ "priority": 100,
17
+ "iso_code": "JPY",
18
+ "name": "Japanese Yen",
19
+ "symbol": "¥",
20
+ "subunit": "Sen",
21
+ "subunit_to_unit": 100,
22
+ "symbol_first": true,
23
+ "html_entity": "¥",
24
+ "decimal_mark": ".",
25
+ "thousands_separator": ",",
26
+ "iso_numeric": ""
27
+ },
28
+ "ghc": {
29
+ "priority": 100,
30
+ "iso_code": "GHS",
31
+ "name": "Ghanaian Cedi",
32
+ "symbol": "₵",
33
+ "subunit": "Pesewa",
34
+ "subunit_to_unit": 100,
35
+ "symbol_first": true,
36
+ "html_entity": "₵",
37
+ "decimal_mark": ".",
38
+ "thousands_separator": ",",
39
+ "iso_numeric": "288"
40
+ }
41
+ }
data/lib/money.rb CHANGED
@@ -1,28 +1,28 @@
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_loader'
26
- require 'money/currency'
27
- require 'money/money'
28
- 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_loader'
26
+ require 'money/currency'
27
+ require 'money/money'
28
+ require 'money/core_extensions'
@@ -1,131 +1,130 @@
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
+ # Given two currency strings or object, checks whether they're both the
110
+ # same currency. Return +true+ if the currencies are the same, +false+
111
+ # otherwise.
112
+ #
113
+ # @param [Money::Currency, String, Symbol] currency1 The first currency
114
+ # to compare.
115
+ # @param [Money::Currency, String, Symbol] currency2 The second currency
116
+ # to compare.
117
+ #
118
+ # @return [Boolean]
119
+ #
120
+ # @example
121
+ # same_currency?("usd", "USD") #=> true
122
+ # same_currency?("usd", "EUR") #=> false
123
+ # same_currency?("usd", Currency.new("USD") #=> true
124
+ # same_currency?("usd", "USD") #=> true
125
+ def same_currency?(currency1, currency2)
126
+ Currency.wrap(currency1) == Currency.wrap(currency2)
127
+ end
128
+ end
129
+ end
130
+ end
@@ -1,252 +1,253 @@
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
1
+ require 'money/bank/base'
2
+
3
+ autoload :JSON, 'json'
4
+ autoload :YAML, 'yaml'
5
+
6
+ class Money
7
+ module Bank
8
+ # Thrown when an unknown rate format is requested.
9
+ class UnknownRateFormat < StandardError; end
10
+
11
+ # Class for aiding in exchanging money between different currencies. By
12
+ # default, the +Money+ class uses an object of this class (accessible
13
+ # through +Money#bank+) for performing currency exchanges.
14
+ #
15
+ # By default, +Money::Bank::VariableExchange+ has no knowledge about
16
+ # conversion rates. One must manually specify them with +add_rate+, after
17
+ # which one can perform exchanges with +#exchange_with+.
18
+ #
19
+ # @example
20
+ # bank = Money::Bank::VariableExchange.new
21
+ # bank.add_rate("USD", "CAD", 1.24515)
22
+ # bank.add_rate("CAD", "USD", 0.803115)
23
+ #
24
+ # c1 = 100_00.to_money("USD")
25
+ # c2 = 100_00.to_money("CAD")
26
+ #
27
+ # # Exchange 100 USD to CAD:
28
+ # bank.exchange_with(c1, "CAD") #=> #<Money @cents=1245150>
29
+ #
30
+ # # Exchange 100 CAD to USD:
31
+ # bank.exchange_with(c2, "USD") #=> #<Money @cents=803115>
32
+ class VariableExchange < Base
33
+
34
+ attr_reader :rates
35
+
36
+ # Available formats for importing/exporting rates.
37
+ RATE_FORMATS = [:json, :ruby, :yaml]
38
+
39
+ # Setup rates hash and mutex for rates locking
40
+ #
41
+ # @return [self]
42
+ def setup
43
+ @rates = {}
44
+ @mutex = Mutex.new
45
+ self
46
+ end
47
+
48
+ def marshal_dump
49
+ [@rates, @rounding_method]
50
+ end
51
+
52
+ def marshal_load(arr)
53
+ @rates, @rounding_method = arr
54
+ @mutex = Mutex.new
55
+ end
56
+
57
+ # Exchanges the given +Money+ object to a new +Money+ object in
58
+ # +to_currency+.
59
+ #
60
+ # @param [Money] from
61
+ # The +Money+ object to exchange.
62
+ # @param [Currency, String, Symbol] to_currency
63
+ # The currency to exchange to.
64
+ #
65
+ # @yield [n] Optional block to use when rounding after exchanging one
66
+ # currency for another.
67
+ # @yieldparam [Float] n The resulting float after exchanging one currency
68
+ # for another.
69
+ # @yieldreturn [Integer]
70
+ #
71
+ # @return [Money]
72
+ #
73
+ # @raise +Money::Bank::UnknownRate+ if the conversion rate is unknown.
74
+ #
75
+ # @example
76
+ # bank = Money::Bank::VariableExchange.new
77
+ # bank.add_rate("USD", "CAD", 1.24515)
78
+ # bank.add_rate("CAD", "USD", 0.803115)
79
+ #
80
+ # c1 = 100_00.to_money("USD")
81
+ # c2 = 100_00.to_money("CAD")
82
+ #
83
+ # # Exchange 100 USD to CAD:
84
+ # bank.exchange_with(c1, "CAD") #=> #<Money @cents=1245150>
85
+ #
86
+ # # Exchange 100 CAD to USD:
87
+ # bank.exchange_with(c2, "USD") #=> #<Money @cents=803115>
88
+ def exchange_with(from, to_currency)
89
+ return from if same_currency?(from.currency, to_currency)
90
+
91
+ rate = get_rate(from.currency, to_currency)
92
+ unless rate
93
+ raise UnknownRate, "No conversion rate known for '#{from.currency.iso_code}' -> '#{to_currency}'"
94
+ end
95
+ _to_currency_ = Currency.wrap(to_currency)
96
+
97
+ 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))
98
+
99
+ ex = cents * BigDecimal.new(rate.to_s)
100
+ ex = ex.to_f
101
+ ex = if block_given?
102
+ yield ex
103
+ elsif @rounding_method
104
+ @rounding_method.call(ex)
105
+ else
106
+ ex.to_s.to_i
107
+ end
108
+ Money.new(ex, _to_currency_)
109
+ end
110
+
111
+ # Registers a conversion rate and returns it (uses +#set_rate+).
112
+ #
113
+ # @param [Currency, String, Symbol] from Currency to exchange from.
114
+ # @param [Currency, String, Symbol] to Currency to exchange to.
115
+ # @param [Numeric] rate Rate to use when exchanging currencies.
116
+ #
117
+ # @return [Numeric]
118
+ #
119
+ # @example
120
+ # bank = Money::Bank::VariableExchange.new
121
+ # bank.add_rate("USD", "CAD", 1.24515)
122
+ # bank.add_rate("CAD", "USD", 0.803115)
123
+ def add_rate(from, to, rate)
124
+ set_rate(from, to, rate)
125
+ end
126
+
127
+ # Set the rate for the given currencies. Uses +Mutex+ to synchronize data
128
+ # access.
129
+ #
130
+ # @param [Currency, String, Symbol] from Currency to exchange from.
131
+ # @param [Currency, String, Symbol] to Currency to exchange to.
132
+ # @param [Numeric] rate Rate to use when exchanging currencies.
133
+ #
134
+ # @return [Numeric]
135
+ #
136
+ # @example
137
+ # bank = Money::Bank::VariableExchange.new
138
+ # bank.set_rate("USD", "CAD", 1.24515)
139
+ # bank.set_rate("CAD", "USD", 0.803115)
140
+ def set_rate(from, to, rate)
141
+ @mutex.synchronize { @rates[rate_key_for(from, to)] = rate }
142
+ end
143
+
144
+ # Retrieve the rate for the given currencies. Uses +Mutex+ to synchronize
145
+ # data access.
146
+ #
147
+ # @param [Currency, String, Symbol] from Currency to exchange from.
148
+ # @param [Currency, String, Symbol] to Currency to exchange to.
149
+ #
150
+ # @return [Numeric]
151
+ #
152
+ # @example
153
+ # bank = Money::Bank::VariableExchange.new
154
+ # bank.set_rate("USD", "CAD", 1.24515)
155
+ # bank.set_rate("CAD", "USD", 0.803115)
156
+ #
157
+ # bank.get_rate("USD", "CAD") #=> 1.24515
158
+ # bank.get_rate("CAD", "USD") #=> 0.803115
159
+ def get_rate(from, to)
160
+ @mutex.synchronize { @rates[rate_key_for(from, to)] }
161
+ end
162
+
163
+ # Return the known rates as a string in the format specified. If +file+
164
+ # is given will also write the string out to the file specified.
165
+ # Available formats are +:json+, +:ruby+ and +:yaml+.
166
+ #
167
+ # @param [Symbol] format Request format for the resulting string.
168
+ # @param [String] file Optional file location to write the rates to.
169
+ #
170
+ # @return [String]
171
+ #
172
+ # @raise +Money::Bank::UnknownRateFormat+ if format is unknown.
173
+ #
174
+ # @example
175
+ # bank = Money::Bank::VariableExchange.new
176
+ # bank.set_rate("USD", "CAD", 1.24515)
177
+ # bank.set_rate("CAD", "USD", 0.803115)
178
+ #
179
+ # s = bank.export_rates(:json)
180
+ # s #=> "{\"USD_TO_CAD\":1.24515,\"CAD_TO_USD\":0.803115}"
181
+ def export_rates(format, file=nil)
182
+ raise Money::Bank::UnknownRateFormat unless
183
+ RATE_FORMATS.include? format
184
+
185
+ s = ""
186
+ @mutex.synchronize {
187
+ s = case format
188
+ when :json
189
+ JSON.dump(@rates)
190
+ when :ruby
191
+ Marshal.dump(@rates)
192
+ when :yaml
193
+ YAML.dump(@rates)
194
+ end
195
+
196
+ unless file.nil?
197
+ File.open(file, "w").write(s)
198
+ end
199
+ }
200
+ s
201
+ end
202
+
203
+ # Loads rates provided in +s+ given the specified format. Available
204
+ # formats are +:json+, +:ruby+ and +:yaml+.
205
+ #
206
+ # @param [Symbol] format The format of +s+.
207
+ # @param [String] s The rates string.
208
+ #
209
+ # @return [self]
210
+ #
211
+ # @raise +Money::Bank::UnknownRateFormat+ if format is unknown.
212
+ #
213
+ # @example
214
+ # s = "{\"USD_TO_CAD\":1.24515,\"CAD_TO_USD\":0.803115}"
215
+ # bank = Money::Bank::VariableExchange.new
216
+ # bank.import_rates(:json, s)
217
+ #
218
+ # bank.get_rate("USD", "CAD") #=> 1.24515
219
+ # bank.get_rate("CAD", "USD") #=> 0.803115
220
+ def import_rates(format, s)
221
+ raise Money::Bank::UnknownRateFormat unless
222
+ RATE_FORMATS.include? format
223
+
224
+ @mutex.synchronize {
225
+ @rates = case format
226
+ when :json
227
+ JSON.load(s)
228
+ when :ruby
229
+ Marshal.load(s)
230
+ when :yaml
231
+ YAML.load(s)
232
+ end
233
+ }
234
+ self
235
+ end
236
+
237
+ private
238
+
239
+ # Return the rate hashkey for the given currencies.
240
+ #
241
+ # @param [Currency, String, Symbol] from The currency to exchange from.
242
+ # @param [Currency, String, Symbol] to The currency to exchange to.
243
+ #
244
+ # @return [String]
245
+ #
246
+ # @example
247
+ # rate_key_for("USD", "CAD") #=> "USD_TO_CAD"
248
+ def rate_key_for(from, to)
249
+ "#{Currency.wrap(from).iso_code}_TO_#{Currency.wrap(to).iso_code}".upcase
250
+ end
251
+ end
252
+ end
253
+ end