money 4.0.1 → 4.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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