cleanconscience-currency 0.7.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. data/COPYING.txt +339 -0
  2. data/ChangeLog +8 -0
  3. data/LICENSE.txt +65 -0
  4. data/Manifest.txt +57 -0
  5. data/README.txt +51 -0
  6. data/Releases.txt +155 -0
  7. data/TODO.txt +9 -0
  8. data/currency.gemspec +18 -0
  9. data/examples/ex1.rb +13 -0
  10. data/examples/xe1.rb +20 -0
  11. data/lib/currency.rb +143 -0
  12. data/lib/currency/active_record.rb +304 -0
  13. data/lib/currency/config.rb +93 -0
  14. data/lib/currency/core_extensions.rb +33 -0
  15. data/lib/currency/currency.rb +172 -0
  16. data/lib/currency/currency/factory.rb +131 -0
  17. data/lib/currency/exception.rb +119 -0
  18. data/lib/currency/exchange.rb +48 -0
  19. data/lib/currency/exchange/rate.rb +214 -0
  20. data/lib/currency/exchange/rate/deriver.rb +157 -0
  21. data/lib/currency/exchange/rate/source.rb +89 -0
  22. data/lib/currency/exchange/rate/source/base.rb +166 -0
  23. data/lib/currency/exchange/rate/source/failover.rb +63 -0
  24. data/lib/currency/exchange/rate/source/federal_reserve.rb +151 -0
  25. data/lib/currency/exchange/rate/source/historical.rb +79 -0
  26. data/lib/currency/exchange/rate/source/historical/rate.rb +185 -0
  27. data/lib/currency/exchange/rate/source/historical/rate_loader.rb +186 -0
  28. data/lib/currency/exchange/rate/source/historical/writer.rb +220 -0
  29. data/lib/currency/exchange/rate/source/new_york_fed.rb +127 -0
  30. data/lib/currency/exchange/rate/source/provider.rb +117 -0
  31. data/lib/currency/exchange/rate/source/test.rb +50 -0
  32. data/lib/currency/exchange/rate/source/the_financials.rb +191 -0
  33. data/lib/currency/exchange/rate/source/timed_cache.rb +198 -0
  34. data/lib/currency/exchange/rate/source/xe.rb +152 -0
  35. data/lib/currency/exchange/time_quantitizer.rb +111 -0
  36. data/lib/currency/formatter.rb +308 -0
  37. data/lib/currency/macro.rb +321 -0
  38. data/lib/currency/money.rb +293 -0
  39. data/lib/currency/money_helper.rb +13 -0
  40. data/lib/currency/parser.rb +196 -0
  41. data/spec/ar_simple_spec.rb +80 -0
  42. data/spec/ar_spec_helper.rb +164 -0
  43. data/spec/config_spec.rb +70 -0
  44. data/spec/federal_reserve_spec.rb +71 -0
  45. data/spec/formatter_spec.rb +72 -0
  46. data/spec/historical_writer_spec.rb +121 -0
  47. data/spec/macro_spec.rb +109 -0
  48. data/spec/money_spec.rb +367 -0
  49. data/spec/new_york_fed_spec.rb +73 -0
  50. data/spec/parser_spec.rb +105 -0
  51. data/spec/spec_helper.rb +25 -0
  52. data/spec/time_quantitizer_spec.rb +115 -0
  53. data/spec/timed_cache_spec.rb +95 -0
  54. data/spec/xe_spec.rb +50 -0
  55. metadata +116 -0
@@ -0,0 +1,121 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+ require File.dirname(__FILE__) + '/ar_spec_helper'
4
+
5
+ require File.dirname(__FILE__) + '/../lib/currency/exchange/rate/source/historical'
6
+ require File.dirname(__FILE__) + '/../lib/currency/exchange/rate/source/historical/writer'
7
+ require File.dirname(__FILE__) + '/../lib/currency/exchange/rate/source/xe'
8
+ require File.dirname(__FILE__) + '/../lib/currency/exchange/rate/source/new_york_fed'
9
+
10
+ include Currency
11
+ RATE_CLASS = Exchange::Rate::Source::Historical::Rate
12
+ TABLE_NAME = RATE_CLASS.table_name
13
+
14
+ class HistoricalRateMigration < ActiveRecord::Migration
15
+ def self.up
16
+ RATE_CLASS.__create_table(self)
17
+ end
18
+
19
+ def self.down
20
+ drop_table TABLE_NAME.to_sym
21
+ end
22
+ end
23
+
24
+ describe "HistoricalWriter" do
25
+ before(:all) do
26
+ ActiveRecord::Base.establish_connection(database_spec)
27
+ @currency_test_migration ||= HistoricalRateMigration
28
+ schema_down
29
+ schema_up
30
+ @xe_src = Exchange::Rate::Source::Xe.new
31
+ @fed_src = Exchange::Rate::Source::NewYorkFed.new
32
+ end
33
+
34
+ after(:all) do
35
+ schema_down
36
+ end
37
+
38
+ def setup_writer(source)
39
+ writer = Exchange::Rate::Source::Historical::Writer.new
40
+ writer.time_quantitizer = :current
41
+ writer.required_currencies = [ :USD, :GBP, :EUR, :CAD ]
42
+ writer.base_currencies = [ :USD ]
43
+ writer.preferred_currencies = writer.required_currencies
44
+ writer.reciprocal_rates = true
45
+ writer.all_rates = true
46
+ writer.identity_rates = false
47
+ writer.source = source
48
+ writer
49
+ end
50
+
51
+ it "can read, parse and write XE data" do
52
+ writer = setup_writer(@xe_src)
53
+ rates = writer.write_rates
54
+ rates.size.should == 12
55
+ assert_h_rates(rates, writer)
56
+ end
57
+
58
+ it "can read, parse and write NewYorkFed data (will fail on weekends)" do
59
+ if @fed_src.available?
60
+ writer = setup_writer(@fed_src)
61
+ rates = writer.write_rates
62
+ rates.size.should == 12
63
+ assert_h_rates(rates, writer)
64
+ end
65
+ end
66
+
67
+ def assert_h_rates(rates, writer = nil)
68
+ hr0 = rates[0]
69
+ hr0.should_not be_nil
70
+ rates.each do | hr |
71
+ found_hr = nil
72
+ begin
73
+ found_hr = hr.find_matching_this(:first)
74
+ found_hr.should_not be_nil
75
+ rescue Object => err
76
+ raise "#{hr.inspect}: #{err}:\n#{err.backtrace.inspect}"
77
+ end
78
+ assert_equal_rate(hr, found_hr)
79
+ assert_rate_defaults(hr, writer)
80
+
81
+ hr.instance_eval do
82
+ date.should == hr0.date
83
+ date_0.should == hr0.date_0
84
+ date_1.should == hr0.date_1
85
+ source.should == hr0.source
86
+ end
87
+ end
88
+ end
89
+
90
+ def assert_equal_rate(hr0, hr)
91
+ tollerance = 0.00001
92
+ hr.c1.to_s.should == hr0.c1.to_s
93
+ hr.c2.to_s.should == hr0.c2.to_s
94
+ hr.source.should == hr0.source
95
+
96
+ hr0.rate.should be_close(hr.rate, tollerance)
97
+ hr0.rate_avg.should be_close(hr.rate_avg, tollerance)
98
+ hr0.rate_samples.should == hr.rate_samples.should
99
+
100
+ hr0.rate_lo.should be_close(hr.rate_lo, tollerance)
101
+ hr0.rate_hi.should be_close(hr.rate_hi, tollerance)
102
+ hr0.rate_date_0.should be_close(hr.rate_date_0, tollerance)
103
+ hr0.rate_date_1.should be_close(hr.rate_date_1, tollerance)
104
+
105
+ hr0.date.should == hr.date
106
+ hr0.date_0.should == hr.date_0
107
+ hr0.date_1.should == hr.date_1
108
+ hr0.derived.should == hr.derived
109
+ end
110
+
111
+ def assert_rate_defaults(hr, writer)
112
+ hr.source.should == writer.source.name
113
+ hr.rate_avg.should == hr.rate
114
+ hr.rate_samples.should == 1
115
+ hr.rate_lo.should == hr.rate
116
+ hr.rate_hi.should == hr.rate
117
+ hr.rate_date_0.should == hr.rate
118
+ hr.rate_date_1.should == hr.rate
119
+ end
120
+
121
+ end
@@ -0,0 +1,109 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+ require 'test/test_base'
5
+ require 'currency'
6
+ require 'currency/macro'
7
+
8
+ module Currency
9
+
10
+ class MacroTest < TestBase
11
+ class Record
12
+ include ::Currency::Macro
13
+
14
+ attr_accessor :date
15
+ attr_accessor :gross
16
+ attr_accessor :tax
17
+ attr_accessor :net
18
+ attr_accessor :currency
19
+
20
+ attr_money :gross_money, :value => :gross, :time => :date, :currency => :currency
21
+ attr_money :tax_money, :value => :tax, :time => :date, :currency => :currency
22
+ attr_money :net_money, :value => :net, :time => :date, :currency => :currency
23
+
24
+ def initialize
25
+ self.date = Time.now
26
+ self.currency = :USD
27
+ end
28
+
29
+ def compute_net
30
+ self.net = self.gross - self.tax
31
+ end
32
+
33
+ def compute_net_money
34
+ self.net_money = self.gross_money - self.tax_money
35
+ end
36
+
37
+ end
38
+
39
+ before do
40
+ super
41
+ end
42
+
43
+ ############################################
44
+ # Tests
45
+ #
46
+
47
+ it "read money" do
48
+ assert_kind_of Record, r = Record.new
49
+ r.gross = 10.00.should_not == nil
50
+ r.gross.should == r.gross_money.to_f
51
+ r.currency.should == r.gross_money.currency.code
52
+ r.date.should == r.gross_money.time
53
+
54
+ r
55
+ end
56
+
57
+
58
+ it "write money rep" do
59
+ assert_kind_of Record, r = Record.new
60
+ r.gross_money = 10.00.should_not == nil
61
+ r.gross.should == r.gross_money.to_f
62
+ r.currency.should == r.gross_money.currency.code
63
+ r.date.should == r.gross_money.time
64
+
65
+ r
66
+ end
67
+
68
+
69
+ it "money cache" do
70
+ r = test_read_money
71
+
72
+ r_gross = r.gross.should_not == nil
73
+ r_gross.object_id.should == r.gross.object_id
74
+
75
+ # Cache flush
76
+ r.gross = 12.00.should_not == nil
77
+ r.gross.should == r.gross_money.to_f
78
+ r_gross.object_id != r.gross.object_id.should_not == nil
79
+ end
80
+
81
+
82
+ it "currency" do
83
+ r = test_read_money
84
+
85
+ r.gross_money.currency.should_not == nil
86
+ r.currency.should == r.gross_money.currency.code
87
+
88
+ end
89
+
90
+
91
+ it "compute" do
92
+ assert_kind_of Record, r = Record.new
93
+ r.gross = 10.00.should_not == nil
94
+ r.tax = 1.50.should_not == nil
95
+ r.compute_net
96
+
97
+ r.net.should == 8.50
98
+ r.net_money.to_f.should == r.net
99
+
100
+ r.compute_net_money
101
+
102
+ r.net.should == 8.50
103
+ r.net_money.to_f.should == r.net
104
+ end
105
+
106
+ end # class
107
+
108
+ end # module
109
+
@@ -0,0 +1,367 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ # require File.dirname(__FILE__) + '/../lib/currency/formatter'
3
+
4
+ describe Currency::Money do
5
+
6
+ before :all do
7
+ Currency::Config.configure do |config|
8
+ config.scale_exp = 6
9
+ end
10
+
11
+ end
12
+
13
+ def zero_money
14
+ @zero_money ||= Currency::Money.new(0)
15
+ end
16
+
17
+ def negative_money
18
+ @negative_money ||= Currency::Money.new(-1.00, :USD)
19
+ end
20
+
21
+ def positive_money
22
+ @positive_money ||= Currency::Money.new(2.99, :USD)
23
+ end
24
+
25
+ it "creates Monies" do
26
+ m = Currency::Money.new(1.99)
27
+ m.should be_kind_of(Currency::Money)
28
+ Currency::Currency.default.should == m.currency
29
+ :USD.should == m.currency.code
30
+ end
31
+
32
+ describe "Converts different objects to Money with the 'money' method" do
33
+ it "works with a float" do
34
+ m = 1.99.money(:USD)
35
+ m.should be_kind_of(Currency::Money)
36
+ :USD.should == m.currency.code
37
+ m.rep.should == 1990000
38
+ end
39
+
40
+ it "works with a FixNum" do
41
+ m = 199.money(:CAD)
42
+ m.should be_kind_of(Currency::Money)
43
+ :CAD.should == m.currency.code
44
+ m.rep.should == 199000000
45
+ end
46
+
47
+ it "works with a string" do
48
+ m = "13.98".money(:CAD)
49
+ m.should be_kind_of(Currency::Money)
50
+ :CAD.should == m.currency.code
51
+ m.rep.should == 13980000
52
+ end
53
+
54
+ it "works with a string again" do
55
+ m = "45.99".money(:EUR)
56
+ m.should be_kind_of(Currency::Money)
57
+ :EUR.should == m.currency.code
58
+ m.rep.should == 45990000
59
+ end
60
+
61
+ describe "deals with voids" do
62
+ it "does not raise with an empty string" do
63
+ lambda{
64
+ Currency::Money.new('')
65
+ }.should_not raise_error
66
+ end
67
+
68
+ it "considers the empty string to be nil " do
69
+ Currency::Money.new('').should be_nil
70
+ end
71
+ end
72
+ end
73
+
74
+ it "deals with zero in the least surprising way" do
75
+ zero_money.negative?.should_not == true
76
+ zero_money.zero?.should_not == nil
77
+ zero_money.positive?.should_not == true
78
+ end
79
+
80
+ it "deals with negatives in the least surprising way" do
81
+ negative_money.negative?.should_not == nil
82
+ negative_money.zero?.should_not == true
83
+ negative_money.positive?.should_not == true
84
+ end
85
+
86
+ it "deals with positive numbers in the least surprising way" do
87
+ positive_money.negative?.should_not == true
88
+ positive_money.zero?.should_not == true
89
+ positive_money.positive?.should_not == nil
90
+ end
91
+
92
+ it "can do comparisons between monies" do
93
+ n = negative_money
94
+ z = zero_money
95
+ p = positive_money
96
+
97
+ negative_money.should < positive_money
98
+ (negative_money > positive_money).should_not be_true
99
+ (positive_money < negative_money).should_not be_true
100
+ (p.should > n)
101
+ (p != n).should_not == nil
102
+
103
+ (z.should <= z)
104
+ (z.should >= z)
105
+
106
+ (z.should <= p)
107
+ (n.should <= z)
108
+ (z.should >= n)
109
+
110
+ n.should == n
111
+ p.should == p
112
+
113
+ z.should == zero_money
114
+ end
115
+
116
+ it "compare" do
117
+ n = negative_money
118
+ z = zero_money
119
+ p = positive_money
120
+
121
+ (n <=> p).should == -1
122
+ (p <=> n).should == 1
123
+ (p <=> z).should == 1
124
+
125
+ (n <=> n).should == 0
126
+ (z <=> z).should == 0
127
+ (p <=> p).should == 0
128
+ end
129
+
130
+ it "rep" do
131
+ m = Currency::Money.new(123, :USD)
132
+ m.should_not == nil
133
+ m.rep.should == 123000000
134
+
135
+ m = Currency::Money.new(123.45, :USD)
136
+ m.should_not == nil
137
+ m.rep.should == 123450000
138
+
139
+ m = Currency::Money.new("123.456", :USD)
140
+ m.should_not == nil
141
+ m.rep.should == 123456000
142
+ end
143
+
144
+ it "convert" do
145
+ m = Currency::Money.new("123.456", :USD)
146
+ m.should_not == nil
147
+ m.rep.should == 123456000
148
+
149
+ m.to_i.should == 123
150
+ m.to_f.should == 123.456
151
+ m.to_s.should == "$123.456000"
152
+ end
153
+
154
+ it "eql" do
155
+ usd1 = Currency::Money.new(123, :USD)
156
+ usd1.should_not == nil
157
+ usd2 = Currency::Money.new("123", :USD)
158
+ usd2.should_not == nil
159
+
160
+ usd1.currency.code.should == :USD
161
+ usd2.currency.code.should == :USD
162
+
163
+ usd2.rep.should == usd1.rep
164
+
165
+ usd1.should == usd2
166
+
167
+ end
168
+
169
+ it "not eql" do
170
+
171
+ usd1 = Currency::Money.new(123, :USD)
172
+ usd1.should_not == nil
173
+ usd2 = Currency::Money.new("123.01", :USD)
174
+ usd2.should_not == nil
175
+
176
+ usd1.currency.code.should == :USD
177
+ usd2.currency.code.should == :USD
178
+
179
+ usd2.rep.should_not == usd1.rep
180
+
181
+ (usd1 != usd2).should_not == nil
182
+
183
+ ################
184
+ # currency !=
185
+ # rep ==
186
+
187
+ usd = Currency::Money.new(123, :USD)
188
+ usd.should_not == nil
189
+ cad = Currency::Money.new(123, :CAD)
190
+ cad.should_not == nil
191
+
192
+ usd.currency.code.should == :USD
193
+ cad.currency.code.should == :CAD
194
+
195
+ cad.rep.should == usd.rep
196
+ (usd.currency != cad.currency).should_not == nil
197
+
198
+ (usd != cad).should_not == nil
199
+
200
+ end
201
+
202
+ describe "operations" do
203
+ before(:each) do
204
+ @usd = Currency::Money.new(123.45, :USD)
205
+ @cad = Currency::Money.new(123.45, :CAD)
206
+ end
207
+
208
+ it "should work" do
209
+ @usd.should_not == nil
210
+ @cad.should_not == nil
211
+ end
212
+
213
+ it "handle negative money" do
214
+ # - Currency::Money => Currency::Money
215
+ (- @usd).rep.should == -123450000
216
+ (- @usd).currency.code.should == :USD
217
+
218
+ (- @cad).rep.should == -123450000
219
+ (- @cad).currency.code.should == :CAD
220
+ end
221
+
222
+ it "should add monies of the same currency" do
223
+ m = (@usd + @usd)
224
+ m.should be_kind_of(Currency::Money)
225
+ m.rep.should == 246900000
226
+ m.currency.code.should == :USD
227
+ end
228
+
229
+ it "should add monies of different currencies and return USD" do
230
+ m = (@usd + @cad)
231
+ m.should be_kind_of(Currency::Money)
232
+ m.rep.should == 228890724
233
+ m.currency.code.should == :USD
234
+ end
235
+
236
+ it "should add monies of different currencies and return CAD" do
237
+ m = (@cad + @usd)
238
+ m.should be_kind_of(Currency::Money)
239
+ m.rep.should == 267985260
240
+ m.currency.code.should == :CAD
241
+ end
242
+
243
+ it "should subtract monies of the same currency" do
244
+ m = (@usd - @usd)
245
+ m.should be_kind_of(Currency::Money)
246
+ m.rep.should == 0
247
+ m.currency.code.should == :USD
248
+ end
249
+
250
+ it "should subtract monies of different currencies and return USD" do
251
+ m = (@usd - @cad)
252
+ m.should be_kind_of(Currency::Money)
253
+ m.rep.should == 18009276
254
+ m.currency.code.should == :USD
255
+ end
256
+
257
+ it "should subtract monies of different currencies and return CAD" do
258
+ m = (@cad - @usd)
259
+ m.should be_kind_of(Currency::Money)
260
+ m.rep.should == -21085260
261
+ m.currency.code.should == :CAD
262
+ end
263
+
264
+ it "should multiply by numerics and return money" do
265
+ m = (@usd * 0.5)
266
+ m.should be_kind_of(Currency::Money)
267
+ m.rep.should == 61725000
268
+ m.currency.code.should == :USD
269
+ end
270
+
271
+ it "should divide by numerics and return money" do
272
+ m = @usd / 3
273
+ m.should be_kind_of(Currency::Money)
274
+ m.rep.should == 41150000
275
+ m.currency.code.should == :USD
276
+ end
277
+
278
+ it "should divide by monies of the same currency and return numeric" do
279
+ m = @usd / Currency::Money.new("41.15", :USD)
280
+ m.should be_kind_of(Numeric)
281
+ m.should be_close(3.0, 1.0e-8)
282
+ end
283
+
284
+ it "should divide by monies of different currencies and return numeric" do
285
+ m = (@usd / @cad)
286
+ m.should be_kind_of(Numeric)
287
+ m.should be_close(Currency::Exchange::Rate::Source::Test.USD_CAD, 0.0001)
288
+ end
289
+ end
290
+
291
+ it "pivot conversions" do
292
+ # Using default get_rate
293
+ cad = Currency::Money.new(123.45, :CAD)
294
+ cad.should_not == nil
295
+ eur = cad.convert(:EUR)
296
+ eur.should_not == nil
297
+ m = (eur.to_f / cad.to_f)
298
+ m.should be_kind_of(Numeric)
299
+ m_expected = (1.0 / Currency::Exchange::Rate::Source::Test.USD_CAD) * Currency::Exchange::Rate::Source::Test.USD_EUR
300
+ m.should be_close(m_expected, 0.001)
301
+
302
+
303
+ gbp = Currency::Money.new(123.45, :GBP)
304
+ gbp.should_not == nil
305
+ eur = gbp.convert(:EUR)
306
+ eur.should_not == nil
307
+ m = (eur.to_f / gbp.to_f)
308
+ m.should be_kind_of(Numeric)
309
+ m_expected = (1.0 / Currency::Exchange::Rate::Source::Test.USD_GBP) * Currency::Exchange::Rate::Source::Test.USD_EUR
310
+ m.should be_close(m_expected, 0.001)
311
+ end
312
+
313
+
314
+ it "invalid currency code" do
315
+ lambda {Currency::Money.new(123, :asdf)}.should raise_error(Currency::Exception::InvalidCurrencyCode)
316
+ lambda {Currency::Money.new(123, 5)}.should raise_error(Currency::Exception::InvalidCurrencyCode)
317
+ end
318
+
319
+
320
+ it "time default" do
321
+ Currency::Money.default_time = nil
322
+
323
+ usd = Currency::Money.new(123.45, :USD)
324
+ usd.should_not == nil
325
+ usd.time.should == nil
326
+
327
+ Currency::Money.default_time = Time.now
328
+ usd = Currency::Money.new(123.45, :USD)
329
+ usd.should_not == nil
330
+ Currency::Money.default_time.should == usd.time
331
+ end
332
+
333
+
334
+ it "time now" do
335
+ Currency::Money.default_time = :now
336
+
337
+ usd = Currency::Money.new(123.45, :USD)
338
+ usd.should_not == nil
339
+ usd.time.should_not == nil
340
+
341
+ sleep 1
342
+
343
+ usd2 = Currency::Money.new(123.45, :USD)
344
+ usd2.should_not == nil
345
+ usd2.time.should_not == nil
346
+ (usd.time != usd2.time).should_not == nil
347
+
348
+ Currency::Money.default_time = nil
349
+ end
350
+
351
+
352
+ it "time fixed" do
353
+ Currency::Money.default_time = Time.new
354
+
355
+ usd = Currency::Money.new(123.45, :USD)
356
+ usd.should_not == nil
357
+ usd.time.should_not == nil
358
+
359
+ sleep 1
360
+
361
+ usd2 = Currency::Money.new(123.45, :USD)
362
+ usd2.should_not == nil
363
+ usd2.time.should_not == nil
364
+ usd.time.should == usd2.time
365
+ end
366
+ end
367
+