exchange 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/README.rdoc +64 -51
- data/Rakefile +2 -2
- data/benchmark/benchmark.rb +6 -5
- data/changelog.rdoc +3 -0
- data/exchange-0.9.0.gem +0 -0
- data/exchange.gemspec +1 -1
- data/lib/exchange/base.rb +7 -1
- data/lib/exchange/core_extensions/float/error_safe.rb +28 -9
- data/lib/exchange/core_extensions/numeric/conversability.rb +22 -20
- data/lib/exchange/money.rb +22 -28
- data/lib/exchange/typecasting.rb +104 -36
- data/spec/exchange/core_extensions/float/error_safe_spec.rb +4 -4
- data/spec/exchange/core_extensions/numeric/conversability_spec.rb +48 -46
- data/spec/exchange/external_api/open_exchange_rates_spec.rb +1 -1
- data/spec/exchange/external_api/xavier_media_spec.rb +1 -1
- data/spec/exchange/money_spec.rb +9 -9
- data/spec/exchange/typecasting_spec.rb +5 -5
- metadata +61 -79
data/Gemfile.lock
CHANGED
data/README.rdoc
CHANGED
@@ -7,21 +7,24 @@ You can use it with just plain ruby projects, in Rails 2 and 3, Sinatra or whate
|
|
7
7
|
== Installation
|
8
8
|
=== Bundler / Rails
|
9
9
|
Add it to your Gemfile
|
10
|
-
gem "exchange", ">=0.
|
10
|
+
gem "exchange", ">=0.10.0"
|
11
11
|
=== Manually
|
12
12
|
Just install it as a gem
|
13
13
|
gem install exchange
|
14
14
|
Then require it
|
15
15
|
require 'exchange'
|
16
|
+
|
17
|
+
=== Deprecations in 0.10.0
|
18
|
+
The old gem API (3.eur, 5.usd.to_chf) has been abandoned in favor of the new (1.in(:eur), 5.in(:usd).to(:chf)). This resolves a conflict of the earlier versions with the active support "try" method, which also stands for turkish lira. It also makes dynamic instantiation of currencies easier (in(currency) is better than send(currency), to(currency) much better than send(:"to_#{currency}")) Version 0.9.0 still supports the old API, so if you don't have the time to change now, just continue to use the old version and change later. There are no other changes than the api and no bugfixes from 0.9.0 to 0.10.0.
|
16
19
|
|
17
20
|
== Features
|
18
21
|
|
19
22
|
=== Easy Conversion
|
20
23
|
|
21
24
|
Conversion of currencies does not get any easier
|
22
|
-
1.eur.
|
25
|
+
1.in(:eur).to(:usd)
|
23
26
|
or better for historic dates
|
24
|
-
1.eur.
|
27
|
+
1.in(:eur).to(:usd, :at => Time.now - 84600)
|
25
28
|
|
26
29
|
|
27
30
|
=== Precise Calculation
|
@@ -36,8 +39,8 @@ Whereas
|
|
36
39
|
|
37
40
|
Exchange uses BigDecimal in all its currency and conversion operations, so you will not be a likely victim for floating point inaccuracies. It even does implicitly convert counterparts of basic operations like (* / - +) to calculate your values safely:
|
38
41
|
|
39
|
-
(50.usd * 0.29).round 0 #=> "USD 15.00"
|
40
|
-
(0.29 * 50.usd).round 0 #=> 15
|
42
|
+
(50.in(:usd) * 0.29).round 0 #=> "USD 15.00"
|
43
|
+
(0.29 * 50.in(:usd)).round 0 #=> 15
|
41
44
|
|
42
45
|
=== BigDecimal? Sounds slow to me
|
43
46
|
|
@@ -76,7 +79,7 @@ Now you're able to do this:
|
|
76
79
|
|
77
80
|
article = Article.new
|
78
81
|
article.price #=> will give you money in the currency defined on the manager
|
79
|
-
article.price = 3.45.usd #Will implicitly convert the price if manager.currency is not :usd
|
82
|
+
article.price = 3.45.in(:usd) #Will implicitly convert the price if manager.currency is not :usd
|
80
83
|
|
81
84
|
You can feed the currency option with a proc or a symbol representing the method. For in-depth information about typecasting {visit the documentation here}[http://rubydoc.info/github/beatrichartz/exchange/Exchange/Typecasting]
|
82
85
|
|
@@ -85,12 +88,12 @@ You can feed the currency option with a proc or a symbol representing the method
|
|
85
88
|
You're hitting the internet only daily to get new rates (hourly updates are available if you're eager to have the absolutely newest ones)
|
86
89
|
|
87
90
|
=== ISO 4217 Currency formatting
|
88
|
-
|
91
|
+
One of the issues with currencies is: You never know the format they should be in. With Exchange, you can just use the currencies
|
89
92
|
to_s method, which takes care of the right format for you. You can either have a string with the currency code in front, or just the amount in the right format
|
90
93
|
|
91
|
-
49.567.usd.to_s #=> "USD 49.57"
|
92
|
-
45.jpy.to_s #=> "JPY 45"
|
93
|
-
34.34.omr.to_s #=> "OMR 34.340"
|
94
|
+
49.567.in(:usd).to_s #=> "USD 49.57"
|
95
|
+
45.in(:jpy).to_s #=> "JPY 45"
|
96
|
+
34.34.in(:omr).to_s #=> "OMR 34.340"
|
94
97
|
|
95
98
|
=== Use three great APIs or your own
|
96
99
|
|
@@ -120,88 +123,88 @@ But, same here, if you don't like any of these or want to use your own caching s
|
|
120
123
|
|
121
124
|
Converting one currency to another is as easy as 1,2,3. Don't be afraid, even if it returns a currency object, all Fixed and Float operations can be applied as method missing routes to the value
|
122
125
|
|
123
|
-
1.usd.
|
124
|
-
2.3.dkk.
|
125
|
-
45.54.nok.
|
126
|
+
1.in(:usd).to(:eur) #=> #<Exchange::Money @value=0.93 @currency=:eur>
|
127
|
+
2.3.in(:dkk).to(:sek) #=> #<Exchange::Money @value=3.33 @currency=:sek>
|
128
|
+
45.54.in(:nok).to(:sek) #=> #<Exchange::Money @value=3.33 @currency=:sek>
|
126
129
|
|
127
130
|
Easily convert one currency to another at a historical rate
|
128
131
|
|
129
|
-
1.52.usd.
|
130
|
-
3.45.eur.
|
131
|
-
345.sek.
|
132
|
+
1.52.in(:usd).to :eur, :at => '2011-01-01' #=> #<Exchange::Money @value=1.23 @currency=:eur>
|
133
|
+
3.45.in(:eur).to :sek, :at => Time.gm(2011,3,3) #=> #<Exchange::Money @value=19.23 @currency=:sek>
|
134
|
+
345.in(:sek).to :nok, :at => Time.gm(2011,3,3) #=> #<Exchange::Money @value=348 @currency=:nok>
|
132
135
|
|
133
136
|
Or even define an instance of currency as historic by adding a time.
|
134
137
|
|
135
|
-
1.52.
|
136
|
-
3.45.
|
137
|
-
345.
|
138
|
+
1.52.in(:usd, :at => '2011-01-01').to(:eur) #=> #<Exchange::Money @value=1.23 @currency=:eur>
|
139
|
+
3.45.in(:usd, :at => Time.gm(2011,3,3)).to(:sek) #=> #<Exchange::Money @value=19.23 @currency=:sek>
|
140
|
+
345.in(:usd, :at => Time.gm(2011,3,3)).to(:nok) #=> #<Exchange::Money @value=348 @currency=:nok>
|
138
141
|
|
139
142
|
Do multiple conversion steps at once (if in any way useful)
|
140
143
|
|
141
|
-
3.chf.
|
144
|
+
3.in(:chf).to(:eur, :at => '2011-02-04').to(:usd) #=> #<Exchange::Money @value=5.3 @currency=:eur>
|
142
145
|
|
143
146
|
|
144
147
|
=== Compare
|
145
148
|
|
146
149
|
Compare Currencies, they will convert implicitly
|
147
150
|
|
148
|
-
2.eur > 2.usd #=> true (2.usd get converted to eur and compared)
|
149
|
-
2.nok < 2.sek #=> false (2.sek get converted to nok and compared)
|
150
|
-
5.eur == 4.34.chf #=> true
|
151
|
-
50.eur == 4.34.chf #=> false
|
152
|
-
50.eur.
|
153
|
-
50.
|
151
|
+
2.in(:eur) > 2.in(:usd) #=> true (2.in(:usd) get converted to eur and compared)
|
152
|
+
2.in(:nok) < 2.in(:sek) #=> false (2.in(:sek) get converted to nok and compared)
|
153
|
+
5.in(:eur) == 4.34.in(:chf) #=> true
|
154
|
+
50.in(:eur) == 4.34.in(:chf) #=> false
|
155
|
+
50.in(:eur).to(:sek) == 50.in(:eur) #=> true
|
156
|
+
50.in(:eur, :at => '2011-1-1') == 50.in(:sek) #=> false
|
154
157
|
|
155
158
|
Sort multiple currencies at once
|
156
159
|
|
157
|
-
[5.eur, 4.usd, 4.
|
160
|
+
[5.in(:eur), 4.in(:usd), 4.in(:chf, :at => '2010-01-01')].sort #=> [#<Exchange::Money @value=4 @currency=:usd>, #<Exchange::Money @value=4 @currency=:chf>, #<Exchange::Money @value=5 @currency=:eur>]
|
158
161
|
|
159
162
|
This is true, because it uses the same historic conversion rate
|
160
163
|
|
161
|
-
3.
|
164
|
+
3.in(:eur, :at => '201-01-01').to(:usd) == 3.in(:eur).to(:usd, :at => '201-01-01')
|
162
165
|
|
163
166
|
But this is false, obviously, because the second instance uses the present exchange rate which differs from the historic one (if the two rates match, this will be true again)
|
164
167
|
|
165
|
-
3.
|
168
|
+
3.in(:eur, :at => '2001-01-01').to(:usd) == 3.in(:eur).to(:usd)
|
166
169
|
|
167
170
|
=== Operate
|
168
171
|
|
169
172
|
Add, Subtract, Multiply, Divide Currencies and don't lose a dime. The result will get returned in the currency of the first argument
|
170
173
|
|
171
|
-
1.usd + 1.32.eur #=> #<Exchange::Money @value=2.54 @currency=:usd>
|
172
|
-
1.usd - 1.32.eur #=> #<Exchange::Money @value=-0.2 @currency=:usd>
|
173
|
-
1.usd * 1.32.eur #=> #<Exchange::Money @value=3.44 @currency=:usd>
|
174
|
-
1.usd / 1.32.eur #=> #<Exchange::Money @value=0.89 @currency=:usd>
|
174
|
+
1.in(:usd) + 1.32.in(:eur) #=> #<Exchange::Money @value=2.54 @currency=:usd>
|
175
|
+
1.in(:usd) - 1.32.in(:eur) #=> #<Exchange::Money @value=-0.2 @currency=:usd>
|
176
|
+
1.in(:usd) * 1.32.in(:eur) #=> #<Exchange::Money @value=3.44 @currency=:usd>
|
177
|
+
1.in(:usd) / 1.32.in(:eur) #=> #<Exchange::Money @value=0.89 @currency=:usd>
|
175
178
|
|
176
179
|
|
177
180
|
If you define a currency object as historic. It will use historic conversion if it gets converted (in this example, the 1.32 eur will get converted to usd at the rate of January 1 2008)
|
178
181
|
|
179
|
-
1.usd - 1.32.
|
182
|
+
1.in(:usd) - 1.32.in(:eur, :at => '2008-1-1') #=> #<Exchange::Money @value=2.54 @currency=:usd>
|
180
183
|
|
181
184
|
You can just instantiate currencies and apply operations. Rounding will by default round the currency to its ISO4217 decimal precision:
|
182
185
|
|
183
|
-
3.123.eur.round #=> #<Exchange::Money @value=3.12 @currency=:eur>
|
186
|
+
3.123.in(:eur).round #=> #<Exchange::Money @value=3.12 @currency=:eur>
|
184
187
|
|
185
188
|
You can also pass the precision you wish for as an argument, round, ceil, floor act like normal:
|
186
189
|
|
187
|
-
3.1234.eur.round(0) #=> #<Exchange::Money @value=3 @currency=:eur>
|
190
|
+
3.1234.in(:eur).round(0) #=> #<Exchange::Money @value=3 @currency=:eur>
|
188
191
|
|
189
192
|
Convert one currency to another and round, ceil or floor it, it still retains currency information of the actual and previous currency
|
190
193
|
|
191
|
-
1.34.usd.
|
192
|
-
10.34.usd.
|
193
|
-
5.34.usd.
|
194
|
-
5.34.usd.
|
194
|
+
1.34.in(:usd).to(:eur).round(0) #=> #<Exchange::Money @value=1 @currency=:eur>
|
195
|
+
10.34.in(:usd).to(:nok).ceil(0) #=> #<Exchange::Money @value=45 @currency=:nok>
|
196
|
+
5.34.in(:usd).to(:eur).floor(0) #=> #<Exchange::Money @value=4 @currency=:eur>
|
197
|
+
5.34.in(:usd).to(:eur).floor.from #=> #<Exchange::Money @value=5.34 @currency=:usd>
|
195
198
|
|
196
199
|
|
197
200
|
=== Retain Information
|
198
201
|
|
199
202
|
Access the original currency and its value after conversion, even over multiple steps
|
200
203
|
|
201
|
-
converted = 2.eur.
|
202
|
-
converted.from
|
203
|
-
converted2 = converted.
|
204
|
-
converted2.from
|
204
|
+
converted = 2.in(:eur).to(:usd) #=> #<Exchange::Money @value=2.12 @currency=:usd>
|
205
|
+
converted.from #=> #<Exchange::Money @value=2 @currency=:eur>
|
206
|
+
converted2 = converted.to(:nok) #=> #<Exchange::Money @value=22.12 @currency=:nok>
|
207
|
+
converted2.from #=> #<Exchange::Money @value=2.12 @currency=:usd>
|
205
208
|
|
206
209
|
== Configuration
|
207
210
|
|
@@ -232,11 +235,20 @@ The options available are
|
|
232
235
|
|
233
236
|
If you're afraid of mixed currency operations, just don't allow them
|
234
237
|
Exchange.configuration.allow_mixed_operations = false
|
235
|
-
1.usd + 1.eur #=> MixedCurrencyError
|
238
|
+
1.in(:usd) + 1.in(:eur) #=> MixedCurrencyError
|
236
239
|
|
237
240
|
=== Caching Options
|
238
241
|
|
239
|
-
|
242
|
+
In Key/Value stores, exchange will cache the API files with a key starting with 'exchange_'
|
243
|
+
|
244
|
+
Use Memory to cache the result (default).
|
245
|
+
Exchange.configuration = Exchange::Configuration.new do |c|
|
246
|
+
c.cache = {
|
247
|
+
:subclass => :memory
|
248
|
+
}
|
249
|
+
end
|
250
|
+
|
251
|
+
Use Memcached to cache the result.
|
240
252
|
Exchange.configuration = Exchange::Configuration.new do |c|
|
241
253
|
c.cache = {
|
242
254
|
:subclass => :memcached,
|
@@ -245,7 +257,7 @@ Use Memcached to cache the result (default). Exchange will cache the API files w
|
|
245
257
|
}
|
246
258
|
end
|
247
259
|
|
248
|
-
Use Redis to cache the result.
|
260
|
+
Use Redis to cache the result.
|
249
261
|
Exchange.configuration = Exchange::Configuration.new do |c|
|
250
262
|
c.cache = {
|
251
263
|
:subclass => :redis,
|
@@ -254,7 +266,7 @@ Use Redis to cache the result. Exchange will cache the API files with a key star
|
|
254
266
|
}
|
255
267
|
end
|
256
268
|
|
257
|
-
Use Rails to cache the result.
|
269
|
+
Use Rails to cache the result.
|
258
270
|
Exchange.configuration = Exchange::Configuration.new do |c|
|
259
271
|
c.cache = {
|
260
272
|
:subclass => :rails
|
@@ -263,17 +275,18 @@ Use Rails to cache the result. Exchange will cache the API files with a key star
|
|
263
275
|
|
264
276
|
=== API Options
|
265
277
|
|
266
|
-
Use the Xaviermedia API
|
278
|
+
Use the Xaviermedia API
|
267
279
|
Exchange.configuration = Exchange::Configuration.new do |c|
|
268
280
|
c.api = {
|
269
281
|
:subclass => :xavier_media
|
270
282
|
}
|
271
283
|
end
|
272
284
|
|
273
|
-
Use the open exchange rates Open Source API
|
285
|
+
Use the open exchange rates Open Source API
|
274
286
|
Exchange.configuration = Exchange::Configuration.new do |c|
|
275
287
|
c.api = {
|
276
|
-
:subclass => :open_exchange_rates
|
288
|
+
:subclass => :open_exchange_rates,
|
289
|
+
:app_id => "Your open exchange rates app id"
|
277
290
|
}
|
278
291
|
end
|
279
292
|
|
data/Rakefile
CHANGED
@@ -19,9 +19,9 @@ Jeweler::Tasks.new do |gem|
|
|
19
19
|
gem.license = "MIT"
|
20
20
|
gem.summary = %Q{Simple Exchange Rate operations for your ruby app}
|
21
21
|
gem.description = %Q{The Exchange Gem gives you easy access to currency functions directly on your Numbers. Imagine a conversion as easy as
|
22
|
-
1.eur.
|
22
|
+
1.in(:eur).to(:usd)
|
23
23
|
or even better
|
24
|
-
1.eur.
|
24
|
+
1.in(:eur).to(:usd, :at => Time.now - 84600)
|
25
25
|
which gets you an exchange at the rates of yesterday.}
|
26
26
|
gem.email = "exchange_gem@gmail.com"
|
27
27
|
gem.authors = ["Beat Richartz"]
|
data/benchmark/benchmark.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'rubygems'
|
1
2
|
require 'benchmark'
|
2
3
|
require 'bigdecimal'
|
3
4
|
|
@@ -6,7 +7,7 @@ class Helper
|
|
6
7
|
begin
|
7
8
|
require a_gem.to_s
|
8
9
|
return true
|
9
|
-
rescue LoadError
|
10
|
+
rescue LoadError
|
10
11
|
puts "You do not have #{a_gem} installed. gem install #{a_gem} to benchmark it\n\n"
|
11
12
|
return false
|
12
13
|
end
|
@@ -31,15 +32,15 @@ two = BigDecimal.new("4.234")
|
|
31
32
|
3.times { results[:big_decimal] << Benchmark.realtime { operations.times { one * two } } }
|
32
33
|
|
33
34
|
if helper.load_or_omit(:money)
|
34
|
-
Money.
|
35
|
+
m = Money.us_dollar(50)
|
35
36
|
results[:money] = []
|
36
|
-
3.times { results[:money] << Benchmark.realtime { operations.times {
|
37
|
+
3.times { results[:money] << Benchmark.realtime { operations.times { m * 0.29 } } }
|
37
38
|
end
|
38
39
|
|
39
40
|
if helper.load_or_omit(:exchange)
|
40
|
-
|
41
|
+
m = 50.in(:usd)
|
41
42
|
results[:exchange] = []
|
42
|
-
3.times { results[:exchange] << Benchmark.realtime { operations.times {
|
43
|
+
3.times { results[:exchange] << Benchmark.realtime { operations.times { m * 0.29 } } }
|
43
44
|
end
|
44
45
|
|
45
46
|
puts "#{operations} operations\n\n"
|
data/changelog.rdoc
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
= Changes to Exchange
|
2
2
|
|
3
|
+
== 0.10.0
|
4
|
+
- Changed the gem API to be less invasive in the numeric classes. The deprecated API had issues with three letter methods defined in active support, mainly the try method. The new API has just two methods, in(currency) and to(currency). There are no known conflicts at this time.
|
5
|
+
|
3
6
|
== 0.9.0
|
4
7
|
- added memory cache support as the new default. This allows you to cache without any external requirement, directly in ruby.
|
5
8
|
- Added a typecasting helper for money to exchange. This allows you to typecast any given attribute into a instance of Exchange::Money
|
data/exchange-0.9.0.gem
ADDED
Binary file
|
data/exchange.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = Exchange::VERSION
|
8
8
|
s.authors = ["Beat Richartz"]
|
9
9
|
s.date = "2012-10-09"
|
10
|
-
s.description = "The Exchange Gem gives you easy access to currency functions directly on your Numbers. Imagine a conversion as easy as \n 1.eur.
|
10
|
+
s.description = "The Exchange Gem gives you easy access to currency functions directly on your Numbers. Imagine a conversion as easy as \n 1.in(:eur).to(:usd) or even better \n 1.in(:eur).to(:usd, :at => Time.now - 84600)\n which gets you an exchange at the rates of yesterday."
|
11
11
|
s.email = "exchange_gem@gmail.com"
|
12
12
|
s.homepage = "http://github.com/beatrichartz/exchange"
|
13
13
|
s.licenses = ["MIT"]
|
data/lib/exchange/base.rb
CHANGED
@@ -2,7 +2,7 @@ module Exchange
|
|
2
2
|
|
3
3
|
# The current version of the exchange gem
|
4
4
|
#
|
5
|
-
VERSION = '0.
|
5
|
+
VERSION = '0.10.0'
|
6
6
|
|
7
7
|
# The root installation path of the gem
|
8
8
|
# @version 0.5
|
@@ -16,4 +16,10 @@ module Exchange
|
|
16
16
|
#
|
17
17
|
NoRateError = Class.new StandardError
|
18
18
|
|
19
|
+
# The error that gets thrown if the given currency is not a currency
|
20
|
+
# @version 0.10
|
21
|
+
# @since 0.10
|
22
|
+
#
|
23
|
+
NoCurrencyError = Class.new(ArgumentError)
|
24
|
+
|
19
25
|
end
|
@@ -4,17 +4,36 @@ module Exchange
|
|
4
4
|
#
|
5
5
|
module ErrorSafe
|
6
6
|
|
7
|
+
# Installs a method chain that overwrites the old error prone meth with the new one
|
8
|
+
#
|
9
|
+
def self.money_error_preventing_method_chain base, meth
|
10
|
+
base.send :alias_method, :"#{meth}with_errors", meth
|
11
|
+
base.send :alias_method, meth, :"#{meth}without_errors"
|
12
|
+
end
|
13
|
+
|
14
|
+
# @!macro prevent_errors_with_exchange_for
|
15
|
+
# Prevents float errors when dealing with instances of Exchange::Money
|
16
|
+
# By Typecasting the float into a Big Decimal
|
17
|
+
# @method $1(other)
|
18
|
+
#
|
19
|
+
def self.prevent_errors_with_exchange_for base, meth
|
20
|
+
base.send(:define_method, :"#{meth}without_errors", lambda { |other|
|
21
|
+
if other.is_a?(Exchange::Money)
|
22
|
+
BigDecimal.new(self.to_s).send(meth, other).to_f
|
23
|
+
else
|
24
|
+
send(:"#{meth}with_errors", other)
|
25
|
+
end
|
26
|
+
})
|
27
|
+
money_error_preventing_method_chain base, meth
|
28
|
+
end
|
29
|
+
|
7
30
|
def self.included base
|
8
31
|
%W(* / + -).each do |meth|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
15
|
-
})
|
16
|
-
base.send :alias_method, :"#{meth}with_errors", meth.to_sym
|
17
|
-
base.send :alias_method, meth.to_sym, :"#{meth}without_errors"
|
32
|
+
|
33
|
+
# @macro prevent_errors_with_exchange_for
|
34
|
+
#
|
35
|
+
prevent_errors_with_exchange_for base, meth.to_sym
|
36
|
+
|
18
37
|
end
|
19
38
|
end
|
20
39
|
|
@@ -1,38 +1,40 @@
|
|
1
1
|
module Exchange
|
2
2
|
|
3
|
-
# The conversability module which will get included in Fixnum and Float, giving them the currency
|
3
|
+
# The conversability module which will get included in Fixnum and Float, giving them the in currency instantiate methods
|
4
4
|
# @author Beat Richartz
|
5
|
-
# @version 0.
|
5
|
+
# @version 0.10
|
6
6
|
# @since 0.1
|
7
7
|
#
|
8
8
|
module Conversability
|
9
9
|
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
10
|
+
# The in method instantiates a money object from a numeric type.
|
11
|
+
# @param [Symbol] currency the currency to instantiate the money with
|
12
|
+
# @param [Hash] options The options to instantiate the currency with
|
13
|
+
# @option [Time] :at The time for which the currency should be instantiated
|
13
14
|
#
|
14
15
|
# @example Instantiate from any type of number
|
15
|
-
# 40.usd => #<Exchange::Money @value=40 @currency=:usd>
|
16
|
-
# -33.nok => #<Exchange::Money @value=-33 @currency=:nok>
|
17
|
-
# 33.333.sek => #<Exchange::Money @value=33.333 @currency=:sek>
|
16
|
+
# 40.in(:usd) => #<Exchange::Money @value=40 @currency=:usd>
|
17
|
+
# -33.in(:nok) => #<Exchange::Money @value=-33 @currency=:nok>
|
18
|
+
# 33.333.in(:sek) => #<Exchange::Money @value=33.333 @currency=:sek>
|
18
19
|
# @example Instantiate and immediatly convert
|
19
|
-
# 1.usd.
|
20
|
-
# 1.nok.
|
21
|
-
# -3.5.dkk
|
20
|
+
# 1.in(:usd).to(:eur) => #<Exchange::Money @value=0.79 @currency=:eur>
|
21
|
+
# 1.in(:nok).to(:chf) => #<Exchange::Money @value=6.55 @currency=:chf>
|
22
|
+
# -3.5.in(:chf).to(:dkk) => #<Exchange::Money @value=-346.55 @currency=:huf>
|
22
23
|
# @example Instantiate and immediatly convert at a specific time in the past
|
23
|
-
# 1.usd.
|
24
|
-
# 1.nok.
|
25
|
-
# -3.5.dkk.
|
24
|
+
# 1.in(:usd).to(:eur, :at => Time.now - 86400) => #<Exchange::Money @value=0.80 @currency=:eur>
|
25
|
+
# 1.in(:nok).to(:chf, :at => Time.now - 3600) => #<Exchange::Money @value=6.57 @currency=:chf>
|
26
|
+
# -3.5.in(:dkk).to(:huf, :at => Time.now - 172800) => #<Exchange::Money @value=-337.40 @currency=:huf>
|
26
27
|
#
|
27
28
|
# @since 0.1
|
28
|
-
# @version 0.
|
29
|
+
# @version 0.10
|
29
30
|
#
|
30
|
-
|
31
|
-
|
32
|
-
Money.new(self,
|
31
|
+
def in currency, options={}
|
32
|
+
if ISO4217.currencies.include? currency
|
33
|
+
Money.new(self, currency, options)
|
34
|
+
else
|
35
|
+
raise Exchange::NoCurrencyError.new("#{currency} is not a currency")
|
33
36
|
end
|
34
|
-
end
|
35
|
-
|
37
|
+
end
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
data/lib/exchange/money.rb
CHANGED
@@ -39,13 +39,13 @@ module Exchange
|
|
39
39
|
# @param [Hash] opts Optional Parameters for instantiation
|
40
40
|
# @option opts [Time] :at The time at which conversion took place
|
41
41
|
# @option opts [String,Symbol] :from The money object this money object was converted from
|
42
|
-
# @version 0.
|
42
|
+
# @version 0.9
|
43
43
|
#
|
44
44
|
# @example Instantiate a money object of 40 US Dollars
|
45
45
|
# Exchange::Money.new(40, :usd)
|
46
46
|
# #=> #<Exchange::Money @number=40.0 @currency=:usd @time=#<Time>>
|
47
47
|
# @example Instantiate a money object of 40 US Dollars and convert it to Euro. It shows the conversion date and the original currency
|
48
|
-
# Exchange::Money.new(40, :usd).
|
48
|
+
# Exchange::Money.new(40, :usd).to(:eur, :at => Time.gm(2012,9,1))
|
49
49
|
# #=> #<Exchange::Money @number=37.0 @currency=:usd @time=#<Time> @from=#<Exchange::Money @number=40.0 @currency=:usd>>
|
50
50
|
#
|
51
51
|
def initialize value, currency_arg=nil, opts={}, &block
|
@@ -61,38 +61,32 @@ module Exchange
|
|
61
61
|
|
62
62
|
# Method missing is used to handle conversions from one money object to another. It only handles currencies which are available in
|
63
63
|
# the API class set in the configuration.
|
64
|
-
# @example
|
65
|
-
# Exchange::Money.new(40,:usd).
|
66
|
-
# @example
|
67
|
-
# Exchange::Money.new(40,:nok).
|
64
|
+
# @example convert to chf
|
65
|
+
# Exchange::Money.new(40,:usd).to(:chf)
|
66
|
+
# @example convert to sek at a given time
|
67
|
+
# Exchange::Money.new(40,:nok).to(:sek, :at => Time.gm(2012,2,2))
|
68
68
|
#
|
69
69
|
def method_missing method, *args, &block
|
70
70
|
value.send method, *args, &block
|
71
71
|
end
|
72
72
|
|
73
|
-
ISO4217.currencies.each do |c|
|
74
|
-
define_method :"to_#{c}" do |*args|
|
75
|
-
if api_supports_currency?(c)
|
76
|
-
convert_to c, { :at => time }.merge(args.first || {})
|
77
|
-
else
|
78
|
-
raise_no_rate_error(c)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
73
|
# Converts this instance of currency into another currency
|
84
74
|
# @return [Exchange::Money] An instance of Exchange::Money with the converted number and the converted currency
|
85
75
|
# @param [Symbol, String] other The currency to convert the number to
|
86
|
-
# @param [Hash]
|
76
|
+
# @param [Hash] options An options hash
|
87
77
|
# @option [Time] :at The timestamp of the rate the conversion took place in
|
88
78
|
# @example convert to 'chf'
|
89
|
-
# Exchange::Money.new(40,:usd).
|
79
|
+
# Exchange::Money.new(40,:usd).to(:chf)
|
90
80
|
# @example convert to 'sek' at a specific rate
|
91
|
-
# Exchange::Money.new(40,:nok).
|
81
|
+
# Exchange::Money.new(40,:nok).to(:sek, :at => Time.gm(2012,2,2))
|
92
82
|
#
|
93
|
-
def
|
94
|
-
|
95
|
-
|
83
|
+
def to other, options={}
|
84
|
+
if api_supports_currency?(other)
|
85
|
+
opts = { :at => time, :from => self }.merge(options)
|
86
|
+
Money.new(api.new.convert(value, currency, other, opts), other, opts)
|
87
|
+
else
|
88
|
+
raise_no_rate_error(other)
|
89
|
+
end
|
96
90
|
end
|
97
91
|
|
98
92
|
class << self
|
@@ -100,7 +94,7 @@ module Exchange
|
|
100
94
|
private
|
101
95
|
|
102
96
|
# @private
|
103
|
-
#
|
97
|
+
# @!macro [attach] install_operation
|
104
98
|
#
|
105
99
|
def install_operation op
|
106
100
|
define_method op do |*precision|
|
@@ -109,14 +103,14 @@ module Exchange
|
|
109
103
|
end
|
110
104
|
|
111
105
|
# @private
|
112
|
-
#
|
106
|
+
# @!macro [attach] base_operation
|
113
107
|
# @method $1(other)
|
114
108
|
#
|
115
109
|
def base_operation op
|
116
110
|
self.class_eval <<-EOV
|
117
111
|
def #{op}(other)
|
118
112
|
test_for_currency_mix_error(other)
|
119
|
-
new_value = value #{op} (other.kind_of?(Money) ? other.
|
113
|
+
new_value = value #{op} (other.kind_of?(Money) ? other.to(self.currency, :at => other.time) : BigDecimal.new(other.to_s))
|
120
114
|
Exchange::Money.new(new_value, currency, :at => time, :from => self)
|
121
115
|
end
|
122
116
|
EOV
|
@@ -253,7 +247,7 @@ module Exchange
|
|
253
247
|
if is_same_currency?(other)
|
254
248
|
other.round.value == self.round.value
|
255
249
|
elsif is_currency?(other)
|
256
|
-
other.
|
250
|
+
other.to(currency, :at => other.time).round.value == self.round.value
|
257
251
|
else
|
258
252
|
value == other
|
259
253
|
end
|
@@ -277,7 +271,7 @@ module Exchange
|
|
277
271
|
if is_same_currency?(other)
|
278
272
|
value <=> other.value
|
279
273
|
elsif is_other_currency?(other)
|
280
|
-
value <=> other.
|
274
|
+
value <=> other.to(currency, :at => other.time).value
|
281
275
|
else
|
282
276
|
value <=> other
|
283
277
|
end
|
@@ -352,7 +346,7 @@ module Exchange
|
|
352
346
|
# @version 0.6
|
353
347
|
#
|
354
348
|
def test_for_currency_mix_error other
|
355
|
-
raise CurrencyMixError.new("You\'re trying to mix up #{currency} with #{other.currency}. You denied mixing currencies in the configuration, allow it or convert the currencies before mixing") if !Exchange.configuration.allow_mixed_operations && other.
|
349
|
+
raise CurrencyMixError.new("You\'re trying to mix up #{currency} with #{other.currency}. You denied mixing currencies in the configuration, allow it or convert the currencies before mixing") if !Exchange.configuration.allow_mixed_operations && other.is_a?(Money) && other.currency != currency
|
356
350
|
end
|
357
351
|
|
358
352
|
# Helper method to raise a no rate error for a given currency if no rate is given
|
data/lib/exchange/typecasting.rb
CHANGED
@@ -16,7 +16,7 @@ module Exchange
|
|
16
16
|
#
|
17
17
|
# end
|
18
18
|
#
|
19
|
-
# MyClass.find(1).update_attributes :price => 1.usd
|
19
|
+
# MyClass.find(1).update_attributes :price => 1.in(:usd)
|
20
20
|
# MyClass.find(1).price #=> 0.77 EUR
|
21
21
|
#
|
22
22
|
# @example The getter sets the currency automatically to the currency set in the definition (example in Ohm)
|
@@ -40,54 +40,122 @@ module Exchange
|
|
40
40
|
# managermanager.update :currency => :usd
|
41
41
|
# my_instance.price #=> instance of exchange currency in usd
|
42
42
|
#
|
43
|
+
# @author Beat Richartz
|
44
|
+
# @since 0.9.0
|
45
|
+
# @version 0.9.0
|
46
|
+
#
|
43
47
|
module Typecasting
|
44
|
-
|
45
|
-
# installs a setter and a getter for the attribute you want to typecast as exchange money
|
46
48
|
|
47
|
-
|
48
|
-
|
49
|
-
|
49
|
+
# Installs a money getter
|
50
|
+
# @!macro [attach] install_money_getter
|
51
|
+
# @method $1
|
52
|
+
#
|
53
|
+
def install_money_getter attribute, options={}
|
50
54
|
|
51
|
-
|
55
|
+
define_method :"#{attribute}_with_exchange_typecasting" do
|
56
|
+
currency = evaluate_money_option(options[:currency]) if options[:currency]
|
57
|
+
|
58
|
+
test_for_currency_error(currency)
|
52
59
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
time =
|
58
|
-
|
59
|
-
Exchange::Money.new(send(:"#{attribute}_without_exchange_typecasting")) do |c|
|
60
|
-
c.currency = currency
|
61
|
-
c.time = time if time
|
62
|
-
end
|
60
|
+
time = evaluate_money_option(options[:at]) if options[:at]
|
61
|
+
|
62
|
+
Exchange::Money.new(send(:"#{attribute}_without_exchange_typecasting")) do |c|
|
63
|
+
c.currency = currency
|
64
|
+
c.time = time if time
|
63
65
|
end
|
64
|
-
|
65
|
-
|
66
|
+
end
|
67
|
+
exchange_typecasting_alias_method_chain attribute
|
66
68
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
end
|
78
|
-
alias_method :"#{attribute}_without_exchange_typecasting=", :"#{attribute}="
|
79
|
-
alias_method :"#{attribute}=", :"#{attribute}_with_exchange_typecasting="
|
69
|
+
end
|
70
|
+
|
71
|
+
# Installs a money setter
|
72
|
+
# @!macro [attach] install_money_setter
|
73
|
+
# @method $1(data)
|
74
|
+
#
|
75
|
+
def install_money_setter attribute, options={}
|
76
|
+
define_method :"#{attribute}_with_exchange_typecasting=" do |data|
|
77
|
+
att = send(attribute)
|
78
|
+
attribute_setter = :"#{attribute}_without_exchange_typecasting="
|
80
79
|
|
80
|
+
if !data.respond_to?(:currency)
|
81
|
+
send(attribute_setter, data)
|
82
|
+
elsif att.currency == data.currency
|
83
|
+
send(attribute_setter, data.value)
|
84
|
+
elsif att.currency != data.currency
|
85
|
+
send(attribute_setter, data.to(att.currency).value)
|
86
|
+
end
|
81
87
|
end
|
82
|
-
|
88
|
+
exchange_typecasting_alias_method_chain attribute, '='
|
89
|
+
end
|
90
|
+
|
91
|
+
# Install an alias method chain for an attribute
|
92
|
+
# @param [String, Symbol] attribute The attribute to install the alias method chain for
|
93
|
+
# @param [String] setter The setter sign ('=') if this is a setter
|
94
|
+
#
|
95
|
+
def exchange_typecasting_alias_method_chain attribute, setter=nil
|
96
|
+
alias_method :"#{attribute}_without_exchange_typecasting#{setter}", :"#{attribute}#{setter}"
|
97
|
+
alias_method :"#{attribute}#{setter}", :"#{attribute}_with_exchange_typecasting#{setter}"
|
98
|
+
end
|
99
|
+
|
100
|
+
# @!macro [attach] install_money_option_eval
|
101
|
+
# @method evaluate_money_option
|
102
|
+
#
|
103
|
+
def install_money_option_eval
|
83
104
|
define_method :evaluate_money_option do |option|
|
84
105
|
option.is_a?(Proc) ? instance_eval(&option) : send(option)
|
85
106
|
end
|
86
107
|
end
|
87
|
-
|
88
|
-
#
|
108
|
+
|
109
|
+
# @!macro [attach] install_currency_error_tester
|
110
|
+
# @method test_for_currency_error
|
89
111
|
#
|
90
|
-
|
112
|
+
def install_currency_error_tester
|
113
|
+
define_method :test_for_currency_error do |currency|
|
114
|
+
raise NoCurrencyError.new("No currency is given for typecasting #{attribute}. Make sure a currency is present") unless currency
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# installs a setter and a getter for the attribute you want to typecast as exchange money
|
119
|
+
# @overload def money(*attributes, options={})
|
120
|
+
# @param [Symbol] attributes The attributes you want to typecast as money.
|
121
|
+
# @param [Hash] options Pass a hash as last argument as options
|
122
|
+
# @option options [Symbol, Proc] :currency The currency to evaluate the money with. Can be a symbol or a proc
|
123
|
+
# @option options [Symbol, Proc] :at The time at which the currency should be casted. All conversions of this currency will take place at this time
|
124
|
+
# @raise [NoCurrencyError] if no currency option is given or the currency evals to nil
|
125
|
+
# @example configure money with symbols, the currency option here will call the method currency in the object context
|
126
|
+
# money :price, :currency => :currency, :time => :created_at
|
127
|
+
# @example configure money with a proc, the proc will be called with the object as an argument. This is equivalent to the example above
|
128
|
+
# money :price, :currency => lambda {|o| o.currency}, :time => lambda{|o| o.created_at}
|
129
|
+
#
|
130
|
+
def money *attributes
|
131
|
+
|
132
|
+
options = attributes.last.is_a?(Hash) ? attributes.pop : {}
|
133
|
+
|
134
|
+
attributes.each do |attribute|
|
135
|
+
|
136
|
+
# Get the attribute typecasted into money
|
137
|
+
# @return [Exchange::Money] an instance of money
|
138
|
+
#
|
139
|
+
install_money_getter attribute, options
|
140
|
+
|
141
|
+
# Set the attribute either with money or just any data
|
142
|
+
# Implicitly converts values given that are not in the same currency as the currency option evaluates to
|
143
|
+
# @param [Exchange::Money, String, Numberic] data The data to set the attribute to
|
144
|
+
#
|
145
|
+
install_money_setter attribute, options
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
# Evaluates options given either as symbols or as procs
|
150
|
+
# @param [Symbol, Proc] option The option to evaluate
|
151
|
+
#
|
152
|
+
install_money_option_eval
|
153
|
+
|
154
|
+
# Evaluates whether an error should be raised because there is no currency present
|
155
|
+
# @param [Symbol] currency The currency, if given
|
156
|
+
#
|
157
|
+
install_currency_error_tester
|
158
|
+
end
|
91
159
|
|
92
160
|
end
|
93
161
|
|
@@ -15,7 +15,7 @@ describe "Exchange::ErrorSafe" do
|
|
15
15
|
describe "money safe calculation" do
|
16
16
|
describe "*" do
|
17
17
|
it "should calculate correctly with exchange money" do
|
18
|
-
(0.29 * 50.usd).round.should == 15
|
18
|
+
(0.29 * 50.in(:usd)).round.should == 15
|
19
19
|
end
|
20
20
|
it "should not touch other operations" do
|
21
21
|
(0.29 * 50).round.should == 14
|
@@ -23,7 +23,7 @@ describe "Exchange::ErrorSafe" do
|
|
23
23
|
end
|
24
24
|
describe "/" do
|
25
25
|
it "should calculate correctly with exchange money" do
|
26
|
-
(((1829.82 / 12.usd) * 100).round.to_f / 100).to_f.should == 152.49
|
26
|
+
(((1829.82 / 12.in(:usd)) * 100).round.to_f / 100).to_f.should == 152.49
|
27
27
|
end
|
28
28
|
it "should not touch other operations" do
|
29
29
|
(((1829.82 / 12) * 100).round.to_f / 100).should == 152.48
|
@@ -31,7 +31,7 @@ describe "Exchange::ErrorSafe" do
|
|
31
31
|
end
|
32
32
|
describe "+" do
|
33
33
|
it "should calculate correctly with exchange money" do
|
34
|
-
(1.0e+25 + BigDecimal.new("9999999999999999900000000").usd).round.to_f.should == 2.0e+25
|
34
|
+
(1.0e+25 + BigDecimal.new("9999999999999999900000000").in(:usd)).round.to_f.should == 2.0e+25
|
35
35
|
end
|
36
36
|
it "should not touch other operations" do
|
37
37
|
(1.0e+25 + BigDecimal.new("9999999999999999900000000")).round.should == 20000000000000001811939328
|
@@ -39,7 +39,7 @@ describe "Exchange::ErrorSafe" do
|
|
39
39
|
end
|
40
40
|
describe "-" do
|
41
41
|
it "should calculate correctly with exchange money" do
|
42
|
-
(1.0e+25 - BigDecimal.new("9999999999999999900000000").usd).round.should == 100000000
|
42
|
+
(1.0e+25 - BigDecimal.new("9999999999999999900000000").in(:usd)).round.should == 100000000
|
43
43
|
end
|
44
44
|
it "should not touch other operations" do
|
45
45
|
(1.0e+25 - BigDecimal.new("9999999999999999900000000")).should == 0
|
@@ -11,92 +11,94 @@ describe "Exchange::Conversability" do
|
|
11
11
|
after(:all) do
|
12
12
|
Exchange.configuration.reset
|
13
13
|
end
|
14
|
-
it "should define all currencies on Fixnum, Float and BigDecimal" do
|
15
|
-
Exchange::ISO4217.definitions.keys.each do |c|
|
16
|
-
1.should be_respond_to(c.to_s.downcase.to_sym)
|
17
|
-
1.1.should be_respond_to(c.to_s.downcase.to_sym)
|
18
|
-
BigDecimal.new("1").should be_respond_to(c.to_s.downcase.to_sym)
|
19
|
-
end
|
20
|
-
end
|
21
14
|
context "with a fixnum" do
|
22
15
|
it "should allow to convert to a currency" do
|
23
|
-
3.eur.should be_kind_of Exchange::Money
|
24
|
-
3.eur.value.should == 3
|
16
|
+
3.in(:eur).should be_kind_of Exchange::Money
|
17
|
+
3.in(:eur).value.should == 3
|
25
18
|
end
|
26
19
|
it "should allow to convert to a curreny with a negative number" do
|
27
|
-
-3.eur.should be_kind_of Exchange::Money
|
28
|
-
-3.eur.value.should == -3
|
20
|
+
-3.in(:eur).should be_kind_of Exchange::Money
|
21
|
+
-3.in(:eur).value.should == -3
|
29
22
|
end
|
30
23
|
it "should allow to do full conversions" do
|
31
24
|
mock_api("http://api.finance.xaviermedia.com/api/2012/08/27.xml", fixture('api_responses/example_xml_api.xml'), 3)
|
32
|
-
3.eur.
|
33
|
-
3.eur.
|
34
|
-
3.eur.
|
25
|
+
3.in(:eur).to(:chf).should be_kind_of Exchange::Money
|
26
|
+
3.in(:eur).to(:chf).value.round(2).should == 3.68
|
27
|
+
3.in(:eur).to(:chf).currency.should == :chf
|
35
28
|
end
|
36
29
|
it "should allow to do full conversions with negative numbers" do
|
37
30
|
mock_api("http://api.finance.xaviermedia.com/api/2012/08/27.xml", fixture('api_responses/example_xml_api.xml'), 3)
|
38
|
-
-3.eur.
|
39
|
-
-3.eur.
|
40
|
-
-3.eur.
|
31
|
+
-3.in(:eur).to(:chf).should be_kind_of Exchange::Money
|
32
|
+
-3.in(:eur).to(:chf).value.round(2).should == -3.68
|
33
|
+
-3.in(:eur).to(:chf).currency.should == :chf
|
41
34
|
end
|
42
35
|
it "should allow to define a historic time in which the currency should be interpreted" do
|
43
|
-
3.
|
44
|
-
3.
|
45
|
-
3.
|
36
|
+
3.in(:chf, :at => Time.gm(2010,1,1)).time.yday.should == 1
|
37
|
+
3.in(:chf, :at => Time.gm(2010,1,1)).time.year.should == 2010
|
38
|
+
3.in(:chf, :at => '2010-01-01').time.year.should == 2010
|
39
|
+
end
|
40
|
+
it "should raise a no currency error if the currency does not exist" do
|
41
|
+
lambda { 35.in(:zzz) }.should raise_error(Exchange::NoCurrencyError, "zzz is not a currency")
|
46
42
|
end
|
47
43
|
end
|
48
44
|
context "with a float" do
|
49
45
|
it "should allow to convert to a currency" do
|
50
|
-
3.25.eur.should be_kind_of Exchange::Money
|
51
|
-
3.25.eur.value.round(2).should == 3.25
|
46
|
+
3.25.in(:eur).should be_kind_of Exchange::Money
|
47
|
+
3.25.in(:eur).value.round(2).should == 3.25
|
52
48
|
end
|
53
49
|
it "should allow to convert to a curreny with a negative number" do
|
54
|
-
-3.25.eur.should be_kind_of Exchange::Money
|
55
|
-
-3.25.eur.value.round(2).should == -3.25
|
50
|
+
-3.25.in(:eur).should be_kind_of Exchange::Money
|
51
|
+
-3.25.in(:eur).value.round(2).should == -3.25
|
56
52
|
end
|
57
53
|
it "should allow to do full conversions" do
|
58
54
|
mock_api("http://api.finance.xaviermedia.com/api/2012/08/27.xml", fixture('api_responses/example_xml_api.xml'), 3)
|
59
|
-
3.25.eur.
|
60
|
-
3.25.eur.
|
61
|
-
3.25.eur.
|
55
|
+
3.25.in(:eur).to(:chf).should be_kind_of Exchange::Money
|
56
|
+
3.25.in(:eur).to(:chf).value.round(2).should == 3.99
|
57
|
+
3.25.in(:eur).to(:chf).currency.should == :chf
|
62
58
|
end
|
63
59
|
it "should allow to do full conversions with negative numbers" do
|
64
60
|
mock_api("http://api.finance.xaviermedia.com/api/2012/08/27.xml", fixture('api_responses/example_xml_api.xml'), 3)
|
65
|
-
-3.25.eur.
|
66
|
-
-3.25.eur.
|
67
|
-
-3.25.eur.
|
61
|
+
-3.25.in(:eur).to(:chf).should be_kind_of Exchange::Money
|
62
|
+
-3.25.in(:eur).to(:chf).value.round(2).should == -3.99
|
63
|
+
-3.25.in(:eur).to(:chf).currency.should == :chf
|
68
64
|
end
|
69
65
|
it "should allow to define a historic time in which the currency should be interpreted" do
|
70
|
-
3.25.
|
71
|
-
3.25.
|
72
|
-
3.25.
|
66
|
+
3.25.in(:chf, :at => Time.gm(2010,1,1)).time.yday.should == 1
|
67
|
+
3.25.in(:chf, :at => Time.gm(2010,1,1)).time.year.should == 2010
|
68
|
+
3.25.in(:chf, :at => '2010-01-01').time.year.should == 2010
|
69
|
+
end
|
70
|
+
it "should raise a no currency error if the currency does not exist" do
|
71
|
+
lambda { 35.23.in(:zzz) }.should raise_error(Exchange::NoCurrencyError, "zzz is not a currency")
|
73
72
|
end
|
74
73
|
end
|
75
74
|
context "with a big decimal" do
|
76
75
|
it "should allow to convert to a currency" do
|
77
|
-
BigDecimal.new("3.25").eur.should be_kind_of Exchange::Money
|
78
|
-
BigDecimal.new("3.25").eur.value.round(2).should == 3.25
|
76
|
+
BigDecimal.new("3.25").in(:eur).should be_kind_of Exchange::Money
|
77
|
+
BigDecimal.new("3.25").in(:eur).value.round(2).should == 3.25
|
79
78
|
end
|
80
79
|
it "should allow to convert to a curreny with a negative number" do
|
81
|
-
BigDecimal.new("-3.25").eur.should be_kind_of Exchange::Money
|
82
|
-
BigDecimal.new("-3.25").eur.value.round(2).should == -3.25
|
80
|
+
BigDecimal.new("-3.25").in(:eur).should be_kind_of Exchange::Money
|
81
|
+
BigDecimal.new("-3.25").in(:eur).value.round(2).should == -3.25
|
83
82
|
end
|
84
83
|
it "should allow to do full conversions" do
|
85
84
|
mock_api("http://api.finance.xaviermedia.com/api/2012/08/27.xml", fixture('api_responses/example_xml_api.xml'), 3)
|
86
|
-
BigDecimal.new("3.25").eur.
|
87
|
-
BigDecimal.new("3.25").eur.
|
88
|
-
BigDecimal.new("3.25").eur.
|
85
|
+
BigDecimal.new("3.25").in(:eur).to(:chf).should be_kind_of Exchange::Money
|
86
|
+
BigDecimal.new("3.25").in(:eur).to(:chf).value.round(2).should == 3.99
|
87
|
+
BigDecimal.new("3.25").in(:eur).to(:chf).currency.should == :chf
|
89
88
|
end
|
90
89
|
it "should allow to do full conversions with negative numbers" do
|
91
90
|
mock_api("http://api.finance.xaviermedia.com/api/2012/08/27.xml", fixture('api_responses/example_xml_api.xml'), 3)
|
92
|
-
BigDecimal.new("-3.25").eur.
|
93
|
-
BigDecimal.new("-3.25").eur.
|
94
|
-
BigDecimal.new("-3.25").eur.
|
91
|
+
BigDecimal.new("-3.25").in(:eur).to(:chf).should be_kind_of Exchange::Money
|
92
|
+
BigDecimal.new("-3.25").in(:eur).to(:chf).value.round(2).should == -3.99
|
93
|
+
BigDecimal.new("-3.25").in(:eur).to(:chf).currency.should == :chf
|
95
94
|
end
|
96
95
|
it "should allow to define a historic time in which the currency should be interpreted" do
|
97
|
-
BigDecimal.new("3.25").
|
98
|
-
BigDecimal.new("3.25").
|
99
|
-
BigDecimal.new("3.25").
|
96
|
+
BigDecimal.new("3.25").in(:chf, :at => Time.gm(2010,1,1)).time.yday.should == 1
|
97
|
+
BigDecimal.new("3.25").in(:chf, :at => Time.gm(2010,1,1)).time.year.should == 2010
|
98
|
+
BigDecimal.new("3.25").in(:chf, :at => '2010-01-01').time.year.should == 2010
|
99
|
+
end
|
100
|
+
it "should raise a no currency error if the currency does not exist" do
|
101
|
+
lambda { BigDecimal.new("3.25").in(:zzz) }.should raise_error(Exchange::NoCurrencyError, "zzz is not a currency")
|
100
102
|
end
|
101
103
|
end
|
102
104
|
end
|
@@ -59,7 +59,7 @@ describe "Exchange::ExternalAPI::OpenExchangeRates" do
|
|
59
59
|
subject.convert(70, :sek, :usd, :at => Time.gm(2011,9,9)).round(2).should == 10.38
|
60
60
|
end
|
61
61
|
it "should convert right when the year is the same, but the yearday is not" do
|
62
|
-
mock_api("https://openexchangerates.org/api/historical/#{Time.now.year}
|
62
|
+
mock_api("https://openexchangerates.org/api/historical/#{Time.now.year}-#{'0' if Time.now.month < 11}#{Time.now.month > 9 ? Time.now.month - 1 : Time.now.month + 1}-01.json?app_id=", fixture('api_responses/example_json_api.json'))
|
63
63
|
subject.convert(70, :sek, :usd, :at => Time.gm(Time.now.year,Time.now.month > 9 ? Time.now.month - 1 : Time.now.month + 1,1)).round(2).should == 10.38
|
64
64
|
end
|
65
65
|
it "should convert right when the yearday is the same, but the year is not" do
|
@@ -55,7 +55,7 @@ describe "Exchange::ExternalAPI::XavierMedia" do
|
|
55
55
|
subject.convert(70, :sek, :usd, :at => Time.gm(2011,9,9)).round(2).should == 10.35
|
56
56
|
end
|
57
57
|
it "should convert right when the year is the same, but the yearday is not" do
|
58
|
-
mock_api("http://api.finance.xaviermedia.com/api/#{Time.now.year}
|
58
|
+
mock_api("http://api.finance.xaviermedia.com/api/#{Time.now.year}/#{'0' if Time.now.month < 11}#{Time.now.month > 9 ? Time.now.month - 1 : Time.now.month + 1}/01.xml", fixture('api_responses/example_xml_api.xml'))
|
59
59
|
subject.convert(70, :sek, :usd, :at => Time.gm(Time.now.year,Time.now.month > 9 ? Time.now.month - 1 : Time.now.month + 1,1)).round(2).should == 10.35
|
60
60
|
end
|
61
61
|
it "should convert right when the yearday is the same, but the year is not" do
|
data/spec/exchange/money_spec.rb
CHANGED
@@ -32,12 +32,12 @@ describe "Exchange::Money" do
|
|
32
32
|
currency.time.should == Time.gm(2012,9,9)
|
33
33
|
end
|
34
34
|
end
|
35
|
-
describe "
|
35
|
+
describe "to" do
|
36
36
|
it "should be able to convert itself to other currencies" do
|
37
37
|
mock_api("http://openexchangerates.org/api/latest.json?app_id=", fixture('api_responses/example_json_api.json'), 3)
|
38
|
-
subject.
|
39
|
-
subject.
|
40
|
-
subject.
|
38
|
+
subject.to(:chf).value.round(2).should == 36.5
|
39
|
+
subject.to(:chf).currency.should == :chf
|
40
|
+
subject.to(:chf).should be_kind_of Exchange::Money
|
41
41
|
end
|
42
42
|
end
|
43
43
|
describe "operations" do
|
@@ -324,7 +324,7 @@ describe "Exchange::Money" do
|
|
324
324
|
let(:comp2) { Exchange::Money.new(40, :usd) }
|
325
325
|
let(:comp3) { Exchange::Money.new(50, :eur) }
|
326
326
|
let(:comp4) { Exchange::Money.new(45, :eur) }
|
327
|
-
let(:comp5) { Exchange::Money.new(50, :eur).
|
327
|
+
let(:comp5) { Exchange::Money.new(50, :eur).to(:usd) }
|
328
328
|
let(:comp6) { Exchange::Money.new(66.1, :usd, :at => Time.gm(2011,1,1)) }
|
329
329
|
before(:each) do
|
330
330
|
mock_api("http://openexchangerates.org/api/latest.json?app_id=", fixture('api_responses/example_json_api.json'), 2)
|
@@ -464,7 +464,7 @@ describe "Exchange::Money" do
|
|
464
464
|
it "should be able to convert via to_currency to other currencies" do
|
465
465
|
mock_api("http://openexchangerates.org/api/latest.json?app_id=", fixture('api_responses/example_json_api.json'), 6)
|
466
466
|
{:chf => 36.5, :usd => 40.0, :dkk => 225.12, :sek => 269.85, :nok => 232.06, :rub => 1205.24}.each do |currency, value|
|
467
|
-
c = subject.
|
467
|
+
c = subject.to(currency)
|
468
468
|
c.value.round(2).should == value
|
469
469
|
c.currency.should == currency
|
470
470
|
end
|
@@ -472,17 +472,17 @@ describe "Exchange::Money" do
|
|
472
472
|
it "should be able to convert via to_currency to other currencies and use historic data" do
|
473
473
|
mock_api("http://openexchangerates.org/api/historical/2011-10-09.json?app_id=", fixture('api_responses/example_json_api.json'), 6)
|
474
474
|
{:chf => 36.5, :usd => 40.0, :dkk => 225.12, :sek => 269.85, :nok => 232.06, :rub => 1205.24}.each do |currency, value|
|
475
|
-
c = subject.
|
475
|
+
c = subject.to(currency, :at => Time.gm(2011,10,9))
|
476
476
|
c.value.round(2).should == value
|
477
477
|
c.currency.should == currency
|
478
478
|
end
|
479
479
|
end
|
480
480
|
it "should use the own time if defined as historic to convert" do
|
481
481
|
mock_api("http://openexchangerates.org/api/historical/2011-01-01.json?app_id=", fixture('api_responses/example_json_api.json'), 2)
|
482
|
-
5.
|
482
|
+
5.in(:eur, :at => Time.gm(2011,1,1)).to(:usd).value.should == 5.in(:eur).to(:usd, :at => Time.gm(2011,1,1)).value
|
483
483
|
end
|
484
484
|
it "should raise errors for currency conversions it does not have rates for" do
|
485
|
-
lambda { subject.
|
485
|
+
lambda { subject.to(:ssp) }.should raise_error(Exchange::NoRateError)
|
486
486
|
end
|
487
487
|
it "should pass on methods it does not understand to its number" do
|
488
488
|
subject.to_f.should == 40
|
@@ -70,16 +70,16 @@ describe "Exchange::Typecasting" do
|
|
70
70
|
end
|
71
71
|
it "should set the value if the given value is numeric" do
|
72
72
|
subject.price = 0.83
|
73
|
-
subject.price.should == 0.83.eur
|
73
|
+
subject.price.should == 0.83.in(:eur)
|
74
74
|
end
|
75
75
|
it "should not convert the value if the given value is in the same currency" do
|
76
|
-
subject.price = 0.83.eur
|
77
|
-
subject.price.should == 0.83.eur
|
76
|
+
subject.price = 0.83.in(:eur)
|
77
|
+
subject.price.should == 0.83.in(:eur)
|
78
78
|
end
|
79
79
|
it "should convert the value if the given value is in another currency" do
|
80
80
|
mock_api("http://api.finance.xaviermedia.com/api/#{Time.now.strftime("%Y/%m/%d")}.xml", fixture('api_responses/example_xml_api.xml'))
|
81
|
-
subject.price = 0.83.usd
|
82
|
-
subject.price.should == 0.62.eur
|
81
|
+
subject.price = 0.83.in(:usd)
|
82
|
+
subject.price.should == 0.62.in(:eur)
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
metadata
CHANGED
@@ -1,84 +1,73 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: exchange
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.10.0
|
5
5
|
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 9
|
9
|
-
- 0
|
10
|
-
version: 0.9.0
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Beat Richartz
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-10-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
21
15
|
name: json
|
22
|
-
|
23
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
24
17
|
none: false
|
25
|
-
requirements:
|
26
|
-
- -
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
hash: 23
|
29
|
-
segments:
|
30
|
-
- 1
|
31
|
-
- 0
|
32
|
-
- 0
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
33
21
|
version: 1.0.0
|
34
22
|
type: :runtime
|
35
|
-
version_requirements: *id001
|
36
|
-
- !ruby/object:Gem::Dependency
|
37
|
-
name: yard
|
38
23
|
prerelease: false
|
39
|
-
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.0.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: yard
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
40
33
|
none: false
|
41
|
-
requirements:
|
42
|
-
- -
|
43
|
-
- !ruby/object:Gem::Version
|
44
|
-
hash: 11
|
45
|
-
segments:
|
46
|
-
- 0
|
47
|
-
- 7
|
48
|
-
- 4
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
49
37
|
version: 0.7.4
|
50
38
|
type: :development
|
51
|
-
version_requirements: *id002
|
52
|
-
- !ruby/object:Gem::Dependency
|
53
|
-
name: bundler
|
54
39
|
prerelease: false
|
55
|
-
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
56
41
|
none: false
|
57
|
-
requirements:
|
58
|
-
- -
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.7.4
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bundler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
65
53
|
version: 1.0.0
|
66
54
|
type: :development
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.0.0
|
62
|
+
description: ! "The Exchange Gem gives you easy access to currency functions directly
|
63
|
+
on your Numbers. Imagine a conversion as easy as \n 1.in(:eur).to(:usd) or even
|
64
|
+
better \n 1.in(:eur).to(:usd, :at => Time.now - 84600)\n which gets you an exchange
|
65
|
+
at the rates of yesterday."
|
74
66
|
email: exchange_gem@gmail.com
|
75
67
|
executables: []
|
76
|
-
|
77
68
|
extensions: []
|
78
|
-
|
79
69
|
extra_rdoc_files: []
|
80
|
-
|
81
|
-
files:
|
70
|
+
files:
|
82
71
|
- .document
|
83
72
|
- .gitignore
|
84
73
|
- .rspec
|
@@ -90,6 +79,7 @@ files:
|
|
90
79
|
- Rakefile
|
91
80
|
- benchmark/benchmark.rb
|
92
81
|
- changelog.rdoc
|
82
|
+
- exchange-0.9.0.gem
|
93
83
|
- exchange.gemspec
|
94
84
|
- iso4217.yml
|
95
85
|
- lib/exchange.rb
|
@@ -158,39 +148,31 @@ files:
|
|
158
148
|
- spec/support/api_responses/example_json_api.json
|
159
149
|
- spec/support/api_responses/example_xml_api.xml
|
160
150
|
homepage: http://github.com/beatrichartz/exchange
|
161
|
-
licenses:
|
151
|
+
licenses:
|
162
152
|
- MIT
|
163
153
|
post_install_message:
|
164
154
|
rdoc_options: []
|
165
|
-
|
166
|
-
require_paths:
|
155
|
+
require_paths:
|
167
156
|
- lib
|
168
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
157
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
169
158
|
none: false
|
170
|
-
requirements:
|
171
|
-
- -
|
172
|
-
- !ruby/object:Gem::Version
|
173
|
-
|
174
|
-
|
175
|
-
- 0
|
176
|
-
version: "0"
|
177
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
159
|
+
requirements:
|
160
|
+
- - ! '>='
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: '0'
|
163
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
178
164
|
none: false
|
179
|
-
requirements:
|
180
|
-
- -
|
181
|
-
- !ruby/object:Gem::Version
|
182
|
-
|
183
|
-
segments:
|
184
|
-
- 0
|
185
|
-
version: "0"
|
165
|
+
requirements:
|
166
|
+
- - ! '>='
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
186
169
|
requirements: []
|
187
|
-
|
188
170
|
rubyforge_project:
|
189
171
|
rubygems_version: 1.8.24
|
190
172
|
signing_key:
|
191
173
|
specification_version: 3
|
192
174
|
summary: Simple Exchange Rate operations for your ruby app
|
193
|
-
test_files:
|
175
|
+
test_files:
|
194
176
|
- spec/exchange/cache/base_spec.rb
|
195
177
|
- spec/exchange/cache/configuration_spec.rb
|
196
178
|
- spec/exchange/cache/file_spec.rb
|