papercavalier-money 3.7.1

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