acvwilson-currency 0.5.0

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 (57) hide show
  1. data/COPYING.txt +339 -0
  2. data/ChangeLog +8 -0
  3. data/LICENSE.txt +65 -0
  4. data/Manifest.txt +58 -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 +265 -0
  13. data/lib/currency/config.rb +91 -0
  14. data/lib/currency/core_extensions.rb +83 -0
  15. data/lib/currency/currency.rb +175 -0
  16. data/lib/currency/currency/factory.rb +121 -0
  17. data/lib/currency/currency_version.rb +6 -0
  18. data/lib/currency/exception.rb +119 -0
  19. data/lib/currency/exchange.rb +48 -0
  20. data/lib/currency/exchange/rate.rb +214 -0
  21. data/lib/currency/exchange/rate/deriver.rb +157 -0
  22. data/lib/currency/exchange/rate/source.rb +89 -0
  23. data/lib/currency/exchange/rate/source/base.rb +166 -0
  24. data/lib/currency/exchange/rate/source/failover.rb +63 -0
  25. data/lib/currency/exchange/rate/source/federal_reserve.rb +160 -0
  26. data/lib/currency/exchange/rate/source/historical.rb +79 -0
  27. data/lib/currency/exchange/rate/source/historical/rate.rb +184 -0
  28. data/lib/currency/exchange/rate/source/historical/rate_loader.rb +186 -0
  29. data/lib/currency/exchange/rate/source/historical/writer.rb +220 -0
  30. data/lib/currency/exchange/rate/source/new_york_fed.rb +127 -0
  31. data/lib/currency/exchange/rate/source/provider.rb +120 -0
  32. data/lib/currency/exchange/rate/source/test.rb +50 -0
  33. data/lib/currency/exchange/rate/source/the_financials.rb +191 -0
  34. data/lib/currency/exchange/rate/source/timed_cache.rb +198 -0
  35. data/lib/currency/exchange/rate/source/xe.rb +165 -0
  36. data/lib/currency/exchange/time_quantitizer.rb +111 -0
  37. data/lib/currency/formatter.rb +310 -0
  38. data/lib/currency/macro.rb +321 -0
  39. data/lib/currency/money.rb +298 -0
  40. data/lib/currency/money_helper.rb +13 -0
  41. data/lib/currency/parser.rb +193 -0
  42. data/spec/ar_column_spec.rb +76 -0
  43. data/spec/ar_core_spec.rb +68 -0
  44. data/spec/ar_simple_spec.rb +23 -0
  45. data/spec/config_spec.rb +29 -0
  46. data/spec/federal_reserve_spec.rb +75 -0
  47. data/spec/formatter_spec.rb +72 -0
  48. data/spec/historical_writer_spec.rb +187 -0
  49. data/spec/macro_spec.rb +109 -0
  50. data/spec/money_spec.rb +355 -0
  51. data/spec/new_york_fed_spec.rb +73 -0
  52. data/spec/parser_spec.rb +105 -0
  53. data/spec/spec_helper.rb +25 -0
  54. data/spec/time_quantitizer_spec.rb +115 -0
  55. data/spec/timed_cache_spec.rb +95 -0
  56. data/spec/xe_spec.rb +50 -0
  57. metadata +117 -0
@@ -0,0 +1,76 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+ # require 'test/ar_test_core'
5
+ # require 'currency'
6
+ #
7
+ # require 'rubygems'
8
+ # require 'active_record'
9
+ # require 'active_record/migration'
10
+ # require 'currency/active_record'
11
+
12
+ require File.dirname(__FILE__) + '/ar_spec_helper'
13
+
14
+ # module Currency
15
+ #
16
+ # class ArFieldTest < ArTestCore
17
+
18
+ ##################################################
19
+ # Basic CurrenyTest AR::B class
20
+ #
21
+
22
+ TABLE_NAME = 'currency_column_test'
23
+
24
+ class CurrencyColumnTestMigration < AR_M
25
+ def self.up
26
+ create_table TABLE_NAME.intern do |t|
27
+ t.column :name, :string
28
+ t.column :amount, :integer # Money
29
+ t.column :amount_currency, :string, :size => 3 # Money.currency.code
30
+ end
31
+ end
32
+
33
+ def self.down
34
+ drop_table TABLE_NAME.intern
35
+ end
36
+ end
37
+
38
+ class CurrencyColumnTest < AR_B
39
+ set_table_name TABLE_NAME
40
+ attr_money :amount, :currency_column => true
41
+ end
42
+
43
+ ##################################################
44
+
45
+
46
+ # def teardown
47
+ # super
48
+ # end
49
+
50
+ ##################################################
51
+
52
+ describe "ActiveRecord macros" do
53
+ before(:all) do
54
+ AR_B.establish_connection(database_spec)
55
+ @currency_test_migration ||= CurrencyColumnTestMigration
56
+ @currency_test ||= CurrencyColumnTest
57
+ # schema_down
58
+ schema_up
59
+ end
60
+
61
+ after(:all) do
62
+ # schema_down
63
+ end
64
+
65
+ it "field" do
66
+ insert_records
67
+
68
+ usd = @currency_test.find(@usd.id)
69
+ usd.should_not be_nil
70
+ assert_equal_currency usd, @usd
71
+
72
+ cad = @currency_test.find(@cad.id)
73
+ cad.should_not be_nil
74
+ assert_equal_currency cad, @cad
75
+ end
76
+ end
@@ -0,0 +1,68 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+
5
+ # =================================================================
6
+ # = TODO: IS THIS STUFF ACTUALLY NEEDED? IF SO, MOVE TO AR HELPER =
7
+ # =================================================================
8
+ require 'test/ar_test_base'
9
+
10
+ module Currency
11
+
12
+ class ArTestCore < ArTestBase
13
+
14
+ ##################################################
15
+ # Basic CurrenyTest AR::B class
16
+ #
17
+
18
+ TABLE_NAME = 'currency_test'
19
+
20
+ class CurrencyTestMigration < AR_M
21
+ def self.up
22
+ create_table TABLE_NAME.intern do |t|
23
+ t.column :name, :string
24
+ t.column :amount, :integer # Money
25
+ end
26
+ end
27
+
28
+ def self.down
29
+ drop_table TABLE_NAME.intern
30
+ end
31
+ end
32
+
33
+
34
+ class CurrencyTest < AR_B
35
+ set_table_name TABLE_NAME
36
+ attr_money :amount
37
+ end
38
+
39
+
40
+ ##################################################
41
+
42
+
43
+ before do
44
+ @currency_test_migration ||= CurrencyTestMigration
45
+ @currency_test ||= CurrencyTest
46
+ super
47
+ end
48
+
49
+
50
+ def teardown
51
+ super
52
+ # schema_down
53
+ end
54
+
55
+
56
+ ##################################################
57
+ #
58
+ #
59
+
60
+
61
+ it "insert" do
62
+ insert_records
63
+ end
64
+
65
+ end
66
+
67
+ end # module
68
+
@@ -0,0 +1,23 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # Copyright (C) 2008 Asa Wilson <acvwilson(at)gmail.com>
3
+ # See LICENSE.txt for details.
4
+
5
+ require File.dirname(__FILE__) + '/ar_spec_helper'
6
+
7
+ describe Currency::ActiveRecord do
8
+ it "simple" do
9
+ # TODO: move insert_records into a before block?
10
+ insert_records
11
+
12
+ usd = @currency_test.find(@usd.id)
13
+ usd.should_not be_nil
14
+ assert_equal_currency usd, @usd
15
+
16
+ cad = @currency_test.find(@cad.id)
17
+ cad.should_not == nil
18
+ assert_equal_money cad, @cad
19
+
20
+ cad.amount.currency.code.should == :USD
21
+ end
22
+ end
23
+
@@ -0,0 +1,29 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Currency::Config do
4
+
5
+ it "should truncate" do
6
+ Currency::Config.configure do | c |
7
+ c.float_ref_filter = Proc.new { | x | x }
8
+
9
+ m = Currency::Money.new(1.999999999)
10
+ m.should be_kind_of(Currency::Money)
11
+ m.rep.should == 1999999
12
+ end
13
+ end
14
+
15
+ it "should round" do
16
+ Currency::Config.configure do | c |
17
+ c.float_ref_filter = Proc.new { | x | x.round }
18
+
19
+ m = Currency::Money.new(1.99999999)
20
+ m.should be_kind_of(Currency::Money)
21
+ m.rep.should == 2000000
22
+ end
23
+
24
+ end
25
+
26
+ end # class
27
+
28
+
29
+
@@ -0,0 +1,75 @@
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
+
6
+ require 'currency' # For :type => :money
7
+ require 'currency/exchange/rate/source/federal_reserve'
8
+
9
+ module Currency
10
+
11
+ class FederalReserveTest < TestBase
12
+ before do
13
+ super
14
+ end
15
+
16
+
17
+ @@available = nil
18
+
19
+ # New York Fed rates are not available on Saturday and Sunday.
20
+ def available?
21
+ if @@available == nil
22
+ @@available = @source.available?
23
+ STDERR.puts "Warning: FederalReserve unavailable on Saturday and Sunday, skipping tests." unless @@available
24
+ end
25
+ @@available
26
+ end
27
+
28
+
29
+ def get_rate_source
30
+ # Force FederalReserve Exchange.
31
+ verbose = false
32
+ source = @source = Exchange::Rate::Source::FederalReserve.new(:verbose => verbose)
33
+ deriver = Exchange::Rate::Deriver.new(:source => source, :verbose => source.verbose)
34
+ end
35
+
36
+
37
+
38
+ it "usd cad" do
39
+ return unless available?
40
+
41
+ # yesterday = Time.now.to_date - 1
42
+
43
+ rates = Exchange::Rate::Source.default.source.raw_rates.should.not == nil
44
+ #assert_not_nil rates[:USD]
45
+ #assert_not_nil usd_cad = rates[:USD][:CAD]
46
+
47
+ usd = Money.new(123.45, :USD).should.not == nil
48
+ cad = usd.convert(:CAD).should.not == nil
49
+
50
+ # assert_kind_of Numeric, m = (cad.to_f / usd.to_f)
51
+ # $stderr.puts "m = #{m}"
52
+ # assert_equal_float usd_cad, m, 0.001
53
+ end
54
+
55
+
56
+ it "cad eur" do
57
+ return unless available?
58
+
59
+ rates = Exchange::Rate::Source.default.source.raw_rates.should.not == nil
60
+ #assert_not_nil rates[:USD]
61
+ #assert_not_nil usd_cad = rates[:USD][:CAD]
62
+ #assert_not_nil usd_eur = rates[:USD][:EUR]
63
+
64
+ cad = Money.new(123.45, :CAD).should.not == nil
65
+ eur = cad.convert(:EUR).should.not == nil
66
+
67
+ #assert_kind_of Numeric, m = (eur.to_f / cad.to_f)
68
+ # $stderr.puts "m = #{m}"
69
+ #assert_equal_float (1.0 / usd_cad) * usd_eur, m, 0.001
70
+ end
71
+
72
+ end
73
+
74
+ end # module
75
+
@@ -0,0 +1,72 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Currency::Formatter do
4
+ before(:each) do
5
+ @time = nil
6
+ @money = Currency::Money.new_rep(1234567890000, :USD, @time)
7
+
8
+ end
9
+
10
+ it "can convert to string" do
11
+ @money.should be_kind_of(Currency::Money)
12
+ @money.currency.should == Currency::Currency.default
13
+ @money.currency.code.should == :USD
14
+ @money.to_s.should == "$1,234,567.890000"
15
+ @money.time.should == @time
16
+ end
17
+
18
+
19
+ it "handles thousands options" do
20
+ @money.to_s(:thousands => false).should == "$1234567.890000"
21
+ @money.to_s(:thousands => true).should == "$1,234,567.890000"
22
+ end
23
+
24
+
25
+ it "handles cents options" do
26
+ @money.to_s(:cents => false).should == "$1,234,567"
27
+ @money.to_s(:cents => true).should == "$1,234,567.890000"
28
+ end
29
+
30
+
31
+ it "handles symbol options" do
32
+ @money.to_s(:symbol => false).should == "1,234,567.890000"
33
+ @money.to_s(:symbol => true).should == "$1,234,567.890000"
34
+ end
35
+
36
+
37
+ it "handles code options" do
38
+ @money.to_s(:code => false).should == "$1,234,567.890000"
39
+ @money.to_s(:code => true).should == "USD $1,234,567.890000"
40
+ end
41
+
42
+
43
+ it "handles html and more" do
44
+ m = ::Currency::Money(12.45, :USD)
45
+ money_string = m.to_s(:html => true, :code => true)
46
+ money_string.should == "<span class=\"currency_code\">USD</span> $12.450000"
47
+
48
+
49
+ m = ::Currency::Money(12.45, :EUR)
50
+ money_string = m.to_s(:html => true, :code => true)
51
+ money_string.should == "<span class=\"currency_code\">EUR</span> &#8364;12.450000"
52
+
53
+ m = ::Currency::Money(12345.45, :EUR)
54
+ money_string = m.to_s(:html => true, :code => true, :thousands_separator => '_')
55
+ money_string.should == "<span class=\"currency_code\">EUR</span> &#8364;12_345.450000"
56
+ end
57
+
58
+
59
+ it "handles time options" do
60
+ time = Time.new
61
+ m = Currency::Money.new_rep(1234567890000, :USD, time)
62
+ m.to_s(:time => false).should == "$1,234,567.890000"
63
+ m.to_s(:time => true).should == "$1,234,567.890000 #{time.getutc.xmlschema(4)}"
64
+ end
65
+
66
+ it "handles decimal options" do
67
+ @money = Currency::Money.new_rep(1234567890000, :USD, @time)
68
+ @money.to_s(:decimals => 2).should == "$1,234,567.89"
69
+ @money.to_s(:decimals => 3).should == "$1,234,567.890"
70
+ @money.to_s(:decimals => 4).should == "$1,234,567.8900"
71
+ end
72
+ end
@@ -0,0 +1,187 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+ require 'test/ar_test_base'
5
+
6
+ require 'rubygems'
7
+ require 'active_record'
8
+ require 'active_record/migration'
9
+
10
+ require 'currency' # For :type => :money
11
+
12
+ require 'currency/exchange/rate/source/historical'
13
+ require 'currency/exchange/rate/source/historical/writer'
14
+ require 'currency/exchange/rate/source/xe'
15
+ require 'currency/exchange/rate/source/new_york_fed'
16
+
17
+
18
+ module Currency
19
+
20
+ class HistoricalWriterTest < ArTestBase
21
+
22
+ RATE_CLASS = Exchange::Rate::Source::Historical::Rate
23
+ TABLE_NAME = RATE_CLASS.table_name
24
+
25
+ class HistoricalRateMigration < AR_M
26
+ def self.up
27
+ RATE_CLASS.__create_table(self)
28
+ end
29
+
30
+ def self.down
31
+ drop_table TABLE_NAME.intern
32
+ end
33
+ end
34
+
35
+
36
+ def initialize(*args)
37
+ @currency_test_migration = HistoricalRateMigration
38
+ super
39
+
40
+ @src = Exchange::Rate::Source::Xe.new
41
+ @src2 = Exchange::Rate::Source::NewYorkFed.new
42
+ end
43
+
44
+
45
+ before do
46
+ super
47
+
48
+ end
49
+
50
+
51
+ it "writer" do
52
+ src = @src.should.not == nil
53
+ writer = Exchange::Rate::Source::Historical::Writer.new().should.not == nil
54
+ writer.time_quantitizer = :current
55
+ writer.required_currencies = [ :USD, :GBP, :EUR, :CAD ]
56
+ writer.base_currencies = [ :USD ]
57
+ writer.preferred_currencies = writer.required_currencies
58
+ writer.reciprocal_rates = true
59
+ writer.all_rates = true
60
+ writer.identity_rates = false
61
+
62
+ writer
63
+ end
64
+
65
+
66
+ def writer_src
67
+ writer = test_writer
68
+ writer.source = @src
69
+ rates = writer.write_rates
70
+ rates.should.not == nil
71
+ rates.size.should > 0
72
+ 12, rates.size.should.not == nil
73
+ assert_h_rates(rates, writer)
74
+ end
75
+
76
+
77
+ def writer_src2
78
+ writer = test_writer
79
+ writer.source = @src2
80
+ return unless writer.source.available?
81
+ rates = writer.write_rates
82
+ rates.should.not == nil
83
+ rates.size.should > 0
84
+ rates.size.should == 12
85
+ assert_h_rates(rates, writer)
86
+ end
87
+
88
+
89
+ def xxx_test_required_failure
90
+ writer = Exchange::Rate::Source::Historical::Writer.new().should.not == nil
91
+ src = @src.should.not == nil
92
+ writer.source = src
93
+ writer.required_currencies = [ :USD, :GBP, :EUR, :CAD, :ZZZ ]
94
+ writer.preferred_currencies = writer.required_currencies
95
+ assert_raises(::RuntimeError) { writer.selected_rates }
96
+ end
97
+
98
+
99
+ it "historical rates" do
100
+ # Make sure there are historical Rates avail for today.
101
+ writer_src
102
+ writer_src2
103
+
104
+ # Force Historical Rate Source.
105
+ source = Exchange::Rate::Source::Historical.new
106
+ deriver = Exchange::Rate::Deriver.new(:source => source)
107
+ Exchange::Rate::Source.default = deriver
108
+
109
+ rates = source.get_raw_rates.should.not == nil
110
+ rates.empty?.should.not == true
111
+ # $stderr.puts "historical rates = #{rates.inspect}"
112
+
113
+ rates = source.get_rates.should.not == nil
114
+ rates.empty?.should.not == true
115
+ # $stderr.puts "historical rates = #{rates.inspect}"
116
+
117
+ m_usd = ::Currency.Money('1234.56', :USD, :now).should.not == nil
118
+ # $stderr.puts "m_usd = #{m_usd.to_s(:code => true)}"
119
+ m_eur = m_usd.convert(:EUR).should.not == nil
120
+ # $stderr.puts "m_eur = #{m_eur.to_s(:code => true)}"
121
+
122
+ end
123
+
124
+
125
+ def assert_h_rates(rates, writer = nil)
126
+ hr0 = rates[0].should.not == nil
127
+ rates.each do | hr |
128
+ found_hr = nil
129
+ begin
130
+ found_hr = hr.find_matching_this(:first).should.not == nil
131
+ rescue Object => err
132
+ raise "#{hr.inspect}: #{err}:\n#{err.backtrace.inspect}"
133
+ end
134
+
135
+ hr0.should.not == nil
136
+
137
+ hr.date.should == hr0.date
138
+ hr.date_0.should == hr0.date_0
139
+ hr.date_1.should == hr0.date_1
140
+ hr.source.should == hr0.source
141
+
142
+ assert_equal_rate(hr, found_hr)
143
+ assert_rate_defaults(hr, writer)
144
+ end
145
+ end
146
+
147
+
148
+ def assert_equal_rate(hr0, hr)
149
+ hr.c1.to_s.should == hr0.c1.to_s
150
+ hr.c2.to_s.should == hr0.c2.to_s
151
+ hr.source.should == hr0.source
152
+ assert_equal_float hr0.rate, hr.rate
153
+ assert_equal_float hr0.rate_avg, hr.rate_avg
154
+ hr.rate_samples.should == hr0.rate_samples
155
+ assert_equal_float hr0.rate_lo, hr.rate_lo
156
+ assert_equal_float hr0.rate_hi, hr.rate_hi
157
+ assert_equal_float hr0.rate_date_0, hr.rate_date_0
158
+ assert_equal_float hr0.rate_date_1, hr.rate_date_1
159
+ hr.date.should == hr0.date
160
+ hr.date_0.should == hr0.date_0
161
+ hr.date_1.should == hr0.date_1
162
+ hr.derived.should == hr0.derived
163
+ end
164
+
165
+
166
+ def assert_rate_defaults(hr, writer)
167
+ hr.source if writer.should == writer.source.name
168
+ hr.rate_avg.should == hr.rate
169
+ 1.should == hr.rate_samples
170
+ hr.rate_lo.should == hr.rate
171
+ hr.rate_hi.should == hr.rate
172
+ hr.rate_date_0.should == hr.rate
173
+ hr.rate_date_1.should == hr.rate
174
+ end
175
+
176
+
177
+ def assert_equal_float(x1, x2, eps = 0.00001)
178
+ eps = (x1 * eps).abs
179
+ assert((x1 - eps) <= x2)
180
+ assert((x1 + eps) >= x2)
181
+ end
182
+
183
+
184
+ end
185
+
186
+ end # module
187
+