currency 0.4.7 → 0.4.9
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +2 -0
- data/Releases.txt +13 -0
- data/TODO.txt +2 -0
- data/bin/currency_historical_rate_load +105 -0
- data/lib/currency/active_record.rb +1 -1
- data/lib/currency/config.rb +1 -1
- data/lib/currency/currency.rb +2 -2
- data/lib/currency/currency/factory.rb +1 -1
- data/lib/currency/currency_version.rb +1 -1
- data/lib/currency/exception.rb +66 -0
- data/lib/currency/exchange.rb +2 -2
- data/lib/currency/exchange/rate.rb +13 -2
- data/lib/currency/exchange/rate/source.rb +1 -1
- data/lib/currency/exchange/rate/source/base.rb +14 -4
- data/lib/currency/exchange/rate/source/failover.rb +8 -2
- data/lib/currency/exchange/rate/source/federal_reserve.rb +2 -2
- data/lib/currency/exchange/rate/source/historical/rate.rb +1 -0
- data/lib/currency/exchange/rate/source/historical/rate_loader.rb +186 -0
- data/lib/currency/exchange/rate/source/historical/writer.rb +11 -2
- data/lib/currency/exchange/rate/source/new_york_fed.rb +5 -1
- data/lib/currency/exchange/rate/source/provider.rb +1 -1
- data/lib/currency/exchange/rate/source/timed_cache.rb +1 -1
- data/lib/currency/exchange/rate/source/xe.rb +27 -6
- data/lib/currency/macro.rb +2 -2
- data/lib/currency/money.rb +1 -1
- data/lib/currency/parser.rb +21 -3
- metadata +35 -13
data/Manifest.txt
CHANGED
@@ -6,6 +6,7 @@ README.txt
|
|
6
6
|
Rakefile
|
7
7
|
Releases.txt
|
8
8
|
TODO.txt
|
9
|
+
bin/currency_historical_rate_load
|
9
10
|
examples/ex1.rb
|
10
11
|
examples/xe1.rb
|
11
12
|
lib/currency.rb
|
@@ -25,6 +26,7 @@ lib/currency/exchange/rate/source/failover.rb
|
|
25
26
|
lib/currency/exchange/rate/source/federal_reserve.rb
|
26
27
|
lib/currency/exchange/rate/source/historical.rb
|
27
28
|
lib/currency/exchange/rate/source/historical/rate.rb
|
29
|
+
lib/currency/exchange/rate/source/historical/rate_loader.rb
|
28
30
|
lib/currency/exchange/rate/source/historical/writer.rb
|
29
31
|
lib/currency/exchange/rate/source/new_york_fed.rb
|
30
32
|
lib/currency/exchange/rate/source/provider.rb
|
data/Releases.txt
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
= Currency Release History
|
2
2
|
|
3
|
+
== Release 0.4.9: 2007/11/01
|
4
|
+
|
5
|
+
CRITICAL FIXES
|
6
|
+
|
7
|
+
* xe.rb - http://xe.com format change: Handle inline div in rates table.
|
8
|
+
* exception.rb - Currency::Exception::Base can take Array with optional key/values.
|
9
|
+
* exception.rb - Additional exception classes.
|
10
|
+
* ALL - use raise instead of throw throughout.
|
11
|
+
|
12
|
+
== Release 0.4.8: 2007/09/04
|
13
|
+
|
14
|
+
* bin/currency_historical_rate_load - script for pulling rates from sources into historical rate table.
|
15
|
+
|
3
16
|
== Release 0.4.7: 2007/06/25
|
4
17
|
|
5
18
|
CRITICAL FIXES
|
data/TODO.txt
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
|
2
2
|
= Currency To Do List
|
3
3
|
|
4
|
+
* Clean up and normalize all exceptions:
|
5
|
+
** Rate sources should add :source => source, etc.
|
4
6
|
* Refactor all configuration class variables into Currency::Config
|
5
7
|
* Refactor all cached values into objects that can be reinstantiated on a per-thread basis
|
6
8
|
* Support http://www.xe.com/ucc/full.php rate queries.
|
@@ -0,0 +1,105 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
# Dependencies
|
5
|
+
bin_dir = File.dirname(__FILE__)
|
6
|
+
$:.unshift File.expand_path(bin_dir + "/../lib")
|
7
|
+
|
8
|
+
require 'rubygems'
|
9
|
+
|
10
|
+
gem 'activesupport'
|
11
|
+
gem 'activerecord'
|
12
|
+
|
13
|
+
require 'active_record'
|
14
|
+
|
15
|
+
require 'currency'
|
16
|
+
require 'currency/exchange/rate/source/historical/rate_loader'
|
17
|
+
require 'optparse'
|
18
|
+
require 'ostruct'
|
19
|
+
require 'yaml'
|
20
|
+
|
21
|
+
|
22
|
+
# Parse command line arguments.
|
23
|
+
opts = { }
|
24
|
+
opts[:db_config] = bin_dir + '/.db_config.yml'
|
25
|
+
opts[:rate_sources] = [ ]
|
26
|
+
opts[:required_currencies] =
|
27
|
+
[
|
28
|
+
:USD,
|
29
|
+
:GBP,
|
30
|
+
:CAD,
|
31
|
+
:EUR,
|
32
|
+
]
|
33
|
+
opts[:RAILS_ENV] = ENV["RAILS_ENV"] || 'development'
|
34
|
+
|
35
|
+
|
36
|
+
op = OptionParser.new do | op |
|
37
|
+
op.banner = "currency_historical_rate_load - loads currency rates from sources into historical rates table"
|
38
|
+
op.separator "Usage:"
|
39
|
+
op.on("--deploy-table",
|
40
|
+
TrueClass,
|
41
|
+
"If true the database table will be created."
|
42
|
+
) do | v |
|
43
|
+
opts[:deploy_table] = v
|
44
|
+
end
|
45
|
+
op.on("-d",
|
46
|
+
"--db-config FILE",
|
47
|
+
String,
|
48
|
+
"The YAML file containing the ActiveRecord::Base database configuration."
|
49
|
+
) do | v |
|
50
|
+
opts[:db_config] = v
|
51
|
+
end
|
52
|
+
op.on("-e",
|
53
|
+
"--rails-env ENV",
|
54
|
+
String,
|
55
|
+
"The configuration key to use from the --db-config file; default #{opts[:RAILS_ENV].inspect}."
|
56
|
+
) do | v |
|
57
|
+
opts[:RAILS_ENV] = v
|
58
|
+
end
|
59
|
+
op.on("-s",
|
60
|
+
"--rate-source RATE_SOURCE",
|
61
|
+
String,
|
62
|
+
"The rate source to be queried."
|
63
|
+
) do | v |
|
64
|
+
opts[:rate_sources] += v.split(/[\s,]+/)
|
65
|
+
end
|
66
|
+
op.on("-c",
|
67
|
+
"--currencies CURRENCY",
|
68
|
+
String,
|
69
|
+
"The required currencies; default: #{opts[:required_currencies].inspect}."
|
70
|
+
) do | v |
|
71
|
+
opts[:required_currencies] = v.split(/[\s,]+/)
|
72
|
+
end
|
73
|
+
op.on("-h",
|
74
|
+
"--help",
|
75
|
+
"Show this message") do
|
76
|
+
STDERR.puts op.to_s
|
77
|
+
exit(1)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
args = ARGV.dup
|
82
|
+
op.parse!(args)
|
83
|
+
|
84
|
+
# Setup the database environment.
|
85
|
+
db_config = File.open(opts[:db_config]) do | fh |
|
86
|
+
YAML::load(fh)
|
87
|
+
end
|
88
|
+
db_config.freeze
|
89
|
+
db_config = db_config[opts[:RAILS_ENV]] || raise("Cannot locate #{opts[:RAILS_ENV].inspect} in --db-config; available environments: #{db_config.keys.sort.inspect}")
|
90
|
+
db_config.freeze
|
91
|
+
ActiveRecord::Base.establish_connection(db_config)
|
92
|
+
|
93
|
+
# Deploy table?
|
94
|
+
if opts[:deploy_table]
|
95
|
+
require 'active_record/migration'
|
96
|
+
Currency::Exchange::Rate::Source::Historical::Rate.__create_table(ActiveRecord::Migration)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Start Loading Rates.
|
100
|
+
instance = Currency::Exchange::Rate::Source::Historical::RateLoader.new(opts)
|
101
|
+
instance.run
|
102
|
+
|
103
|
+
# Finished
|
104
|
+
exit(0)
|
105
|
+
|
@@ -235,7 +235,7 @@ def #{attr_name}=(value)
|
|
235
235
|
#{write_preferred_currency}
|
236
236
|
#{write_currency ? write_currency : "#{attr_name}_money = #{attr_name}_money.convert(#{currency})"}
|
237
237
|
else
|
238
|
-
|
238
|
+
raise ::Currency::Exception::InvalidMoneyValue, value
|
239
239
|
end
|
240
240
|
|
241
241
|
@#{attr_name} = #{attr_name}_money
|
data/lib/currency/config.rb
CHANGED
@@ -32,7 +32,7 @@ class Currency::Config
|
|
32
32
|
def self.current
|
33
33
|
Thread.current[:Currency__Config] ||=
|
34
34
|
self.default ||
|
35
|
-
(raise ::Currency::Exception::UndefinedConfig
|
35
|
+
(raise ::Currency::Exception::UndefinedConfig, "Currency::Config.default not defined")
|
36
36
|
end
|
37
37
|
|
38
38
|
# Sets the current Currency::Config object used
|
data/lib/currency/currency.rb
CHANGED
@@ -69,8 +69,8 @@ class Currency::Currency
|
|
69
69
|
# Symbol format.
|
70
70
|
def self.cast_code(x)
|
71
71
|
x = x.upcase.intern if x.kind_of?(String)
|
72
|
-
raise ::Currency::Exception::InvalidCurrencyCode
|
73
|
-
raise ::Currency::Exception::InvalidCurrencyCode
|
72
|
+
raise ::Currency::Exception::InvalidCurrencyCode, x unless x.kind_of?(Symbol)
|
73
|
+
raise ::Currency::Exception::InvalidCurrencyCode, x unless x.to_s.length == 3
|
74
74
|
x
|
75
75
|
end
|
76
76
|
|
@@ -83,7 +83,7 @@ class Currency::Currency::Factory
|
|
83
83
|
|
84
84
|
# Installs a new Currency for #get_by_symbol and #get_by_code.
|
85
85
|
def install(currency)
|
86
|
-
raise ::Currency::Exception::UnknownCurrency
|
86
|
+
raise ::Currency::Exception::UnknownCurrency unless currency
|
87
87
|
@currency_by_symbol[currency.symbol] ||= currency unless currency.symbol.nil?
|
88
88
|
@currency_by_code[currency.code] = currency
|
89
89
|
end
|
data/lib/currency/exception.rb
CHANGED
@@ -3,7 +3,62 @@
|
|
3
3
|
|
4
4
|
module Currency::Exception
|
5
5
|
# Base class for all Currency::Exception objects.
|
6
|
+
#
|
7
|
+
# raise Currency::Exception [ "msg", :opt1, 1, :opt2, 2 ]
|
8
|
+
#
|
6
9
|
class Base < ::Exception
|
10
|
+
EMPTY_HASH = { }.freeze
|
11
|
+
|
12
|
+
def initialize(arg1, *args)
|
13
|
+
case arg1
|
14
|
+
# [ description, ... ]
|
15
|
+
when Array
|
16
|
+
@opts = arg1
|
17
|
+
arg1 = arg1.shift
|
18
|
+
else
|
19
|
+
@opts = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
case @opts
|
23
|
+
when Array
|
24
|
+
if @opts.size == 1 && @opts.first.kind_of?(Hash)
|
25
|
+
# [ description, { ... } ]
|
26
|
+
@opts = @opts.first
|
27
|
+
else
|
28
|
+
# [ description, :key, value, ... ]
|
29
|
+
@opts = Hash[*@opts]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
case @opts
|
34
|
+
when Hash
|
35
|
+
@opts = @opts.dup.freeze
|
36
|
+
else
|
37
|
+
@opts = { :info => @opts }.freeze
|
38
|
+
end
|
39
|
+
|
40
|
+
@opts ||= EMPTY_HASH
|
41
|
+
|
42
|
+
super(arg1, *args)
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def method_missing(sel, *args, &blk)
|
47
|
+
sel = sel.to_sym
|
48
|
+
if args.empty? && ! block_given? && @opts.key?(sel)
|
49
|
+
return @opts[sel]
|
50
|
+
end
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
super + ": #{@opts.inspect}"
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
# Generic Error.
|
61
|
+
class Generic < Base
|
7
62
|
end
|
8
63
|
|
9
64
|
# Error during parsing of Money values from String.
|
@@ -22,6 +77,10 @@ module Currency::Exception
|
|
22
77
|
class IncompatibleCurrency < Base
|
23
78
|
end
|
24
79
|
|
80
|
+
# Error during locating currencies.
|
81
|
+
class MissingCurrency < Base
|
82
|
+
end
|
83
|
+
|
25
84
|
# Error if an Exchange is not defined.
|
26
85
|
class UndefinedExchange < Base
|
27
86
|
end
|
@@ -49,5 +108,12 @@ module Currency::Exception
|
|
49
108
|
# Error if a subclass is responsible for implementing a method.
|
50
109
|
class SubclassResponsibility < Base
|
51
110
|
end
|
111
|
+
|
112
|
+
# Error if some functionality is unimplemented
|
113
|
+
class Unimplemented < Base
|
114
|
+
end
|
52
115
|
|
116
|
+
# Error if reentrantancy is d
|
117
|
+
class InvalidReentrancy < Base
|
118
|
+
end
|
53
119
|
end # module
|
data/lib/currency/exchange.rb
CHANGED
@@ -20,7 +20,7 @@ module Currency::Exchange
|
|
20
20
|
# created. Currency::Exchange::Base cannot service any
|
21
21
|
# conversion rate requests.
|
22
22
|
def self.default
|
23
|
-
@@default ||= raise
|
23
|
+
@@default ||= raise :Currency::Exception::Unimplemented, :default
|
24
24
|
end
|
25
25
|
|
26
26
|
# Sets the default Currency::Exchange object.
|
@@ -34,7 +34,7 @@ module Currency::Exchange
|
|
34
34
|
# If #current= has not been called and #default= has not been called,
|
35
35
|
# then UndefinedExchange is raised.
|
36
36
|
def self.current
|
37
|
-
@@current || self.default || (raise ::Currency::Exception::UndefinedExchange
|
37
|
+
@@current || self.default || (raise ::Currency::Exception::UndefinedExchange, "Currency::Exchange.current not defined")
|
38
38
|
end
|
39
39
|
|
40
40
|
# Sets the current Currency::Exchange object used during
|
@@ -57,7 +57,13 @@ class Currency::Exchange::Rate
|
|
57
57
|
@c1 = c1
|
58
58
|
@c2 = c2
|
59
59
|
@rate = c1_to_c2_rate
|
60
|
-
raise ::Currency::Exception::InvalidRate
|
60
|
+
raise ::Currency::Exception::InvalidRate,
|
61
|
+
[
|
62
|
+
"rate is not positive",
|
63
|
+
:rate, @rate,
|
64
|
+
:c1, c1,
|
65
|
+
:c2, c2,
|
66
|
+
] unless @rate && @rate >= 0.0
|
61
67
|
@source = source
|
62
68
|
@date = date
|
63
69
|
@derived = derived
|
@@ -139,7 +145,12 @@ class Currency::Exchange::Rate
|
|
139
145
|
if @c1 == rate.c2 && @c2 == rate.c1
|
140
146
|
collect_rate(rate.reciprocal)
|
141
147
|
elsif ! (@c1 == rate.c1 && @c2 == rate.c2)
|
142
|
-
raise
|
148
|
+
raise ::Currency::Exception::InvalidRate,
|
149
|
+
[
|
150
|
+
"Cannot collect rates between different currency pairs",
|
151
|
+
:rate1, self,
|
152
|
+
:rate2, rate,
|
153
|
+
]
|
143
154
|
else
|
144
155
|
# Multisource?
|
145
156
|
@source = "<<multiple-sources>>" unless @source == rate.source
|
@@ -76,7 +76,7 @@ module Currency::Exchange::Rate::Source
|
|
76
76
|
# If #current= has not been called and #default= has not been called,
|
77
77
|
# then UndefinedExchange is raised.
|
78
78
|
def self.current
|
79
|
-
@@current || self.default || (raise ::Currency::Exception::UndefinedExchange
|
79
|
+
@@current || self.default || (raise ::Currency::Exception::UndefinedExchange, "Currency::Exchange.current not defined")
|
80
80
|
end
|
81
81
|
|
82
82
|
# Sets the current Currency::Exchange object used during
|
@@ -41,6 +41,16 @@ class Currency::Exchange::Rate::Source::Base
|
|
41
41
|
end
|
42
42
|
|
43
43
|
|
44
|
+
def __subclass_responsibility(meth)
|
45
|
+
raise ::Currency::Exception::SubclassResponsibility,
|
46
|
+
[
|
47
|
+
"#{self.class}#\#{meth}",
|
48
|
+
:class, self.class,
|
49
|
+
:method, method,
|
50
|
+
]
|
51
|
+
end
|
52
|
+
|
53
|
+
|
44
54
|
# Converts Money m in Currency c1 to a new
|
45
55
|
# Money value in Currency c2.
|
46
56
|
def convert(m, c2, time = nil, c1 = nil)
|
@@ -90,7 +100,7 @@ class Currency::Exchange::Rate::Source::Base
|
|
90
100
|
# Gets all rates available by this source.
|
91
101
|
#
|
92
102
|
def rates(time = nil)
|
93
|
-
|
103
|
+
__subclass_responsibility(:rates)
|
94
104
|
end
|
95
105
|
|
96
106
|
|
@@ -108,14 +118,14 @@ class Currency::Exchange::Rate::Source::Base
|
|
108
118
|
# rates.
|
109
119
|
#
|
110
120
|
def get_rate(c1, c2, time)
|
111
|
-
|
121
|
+
__subclass_responsibility(:get_rate)
|
112
122
|
end
|
113
123
|
|
114
124
|
# Returns a base Rate.
|
115
125
|
#
|
116
126
|
# Subclasses are required to implement this method.
|
117
127
|
def get_rate_base(c1, c2, time)
|
118
|
-
|
128
|
+
__subclass_responsibility(:get_rate_base)
|
119
129
|
end
|
120
130
|
|
121
131
|
|
@@ -123,7 +133,7 @@ class Currency::Exchange::Rate::Source::Base
|
|
123
133
|
#
|
124
134
|
# Subclasses must override this method.
|
125
135
|
def get_rates(time = nil)
|
126
|
-
|
136
|
+
__subclass_responsibility(:get_rates)
|
127
137
|
end
|
128
138
|
|
129
139
|
|
@@ -38,13 +38,19 @@ class Currency::Exchange::Rate::Source::Failover < ::Currency::Exchange::Base
|
|
38
38
|
|
39
39
|
|
40
40
|
if rate == nil || err
|
41
|
-
$stderr.
|
41
|
+
$stderr.puts "Failover: primary failed for get_rate(#{c1}, #{c2}, #{time}) : #{err.inspect}"
|
42
42
|
rate = @secondary.get_rate(c1, c2, time)
|
43
43
|
end
|
44
44
|
|
45
45
|
|
46
46
|
unless rate
|
47
|
-
raise
|
47
|
+
raise Currency::Exception::UnknownRate,
|
48
|
+
[
|
49
|
+
"Failover: secondary failed for get_rate(#{c1}, #{c2}, #{time})",
|
50
|
+
:c1, c1,
|
51
|
+
:c2, c2,
|
52
|
+
:time, time,
|
53
|
+
]
|
48
54
|
end
|
49
55
|
|
50
56
|
rate
|
@@ -101,7 +101,7 @@ class Currency::Exchange::Rate::Source::FederalReserve < ::Currency::Exchange::R
|
|
101
101
|
c1, c2 = @@country_to_currency[country_code]
|
102
102
|
|
103
103
|
unless c1 && c2
|
104
|
-
raise
|
104
|
+
raise ::Currency::Exception::UnavailableRates, "Cannot determine currency code for federalreserve.gov country code #{country_code.inspect}"
|
105
105
|
end
|
106
106
|
|
107
107
|
data.split(/\r?\n\r?/).each do | line |
|
@@ -122,7 +122,7 @@ class Currency::Exchange::Rate::Source::FederalReserve < ::Currency::Exchange::R
|
|
122
122
|
|
123
123
|
rate = m[4].to_f
|
124
124
|
|
125
|
-
STDERR.puts "#{c1} #{c2} #{rate}
|
125
|
+
STDERR.puts "#{c1} #{c2} #{rate}\t#{date}" if @verbose
|
126
126
|
|
127
127
|
rates << new_rate(c1, c2, rate, date)
|
128
128
|
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'currency/exchange/rate/source/historical'
|
2
|
+
require 'currency/exchange/rate/source/historical/rate'
|
3
|
+
require 'currency/exchange/rate/source/historical/writer'
|
4
|
+
|
5
|
+
|
6
|
+
# Currency::Config.current.historical_table_name = 'currency_rates'
|
7
|
+
# opts['uri_path'] ||= 'syndicated/cnusa/fxrates.xml'
|
8
|
+
|
9
|
+
# Loads rates from multiple sources and will store them
|
10
|
+
# as historical rates in a database.
|
11
|
+
|
12
|
+
class ::Currency::Exchange::Rate::Source::Historical::RateLoader
|
13
|
+
attr_accessor :options
|
14
|
+
attr_accessor :source_options
|
15
|
+
attr_accessor :required_currencies
|
16
|
+
attr_accessor :rate_sources
|
17
|
+
attr_accessor :rate_source_options
|
18
|
+
attr_accessor :verbose
|
19
|
+
attr_accessor :preferred_summary_source
|
20
|
+
attr_accessor :base_currencies
|
21
|
+
attr_accessor :summary_rate_src
|
22
|
+
attr_reader :writer
|
23
|
+
|
24
|
+
def initialize(opts = { })
|
25
|
+
self.summary_rate_src = 'summary'
|
26
|
+
self.source_options = { }
|
27
|
+
self.options = opts.dup.freeze
|
28
|
+
self.base_currencies = [ :USD ]
|
29
|
+
self.required_currencies =
|
30
|
+
[
|
31
|
+
:USD,
|
32
|
+
:GBP,
|
33
|
+
:CAD,
|
34
|
+
:EUR,
|
35
|
+
# :MXP,
|
36
|
+
]
|
37
|
+
self.verbose = ! ! ENV['CURRENCY_VERBOSE']
|
38
|
+
opts.each do | k, v |
|
39
|
+
setter = "#{k}="
|
40
|
+
send(setter, v) if respond_to?(setter)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def initialize_writer(writer = Currency::Exchange::Rate::Source::Historical::Writer.new)
|
46
|
+
@writer = writer
|
47
|
+
|
48
|
+
writer.time_quantitizer = :current
|
49
|
+
writer.required_currencies = required_currencies
|
50
|
+
writer.base_currencies = base_currencies
|
51
|
+
writer.preferred_currencies = writer.required_currencies
|
52
|
+
writer.reciprocal_rates = true
|
53
|
+
writer.all_rates = true
|
54
|
+
writer.identity_rates = false
|
55
|
+
|
56
|
+
options.each do | k, v |
|
57
|
+
setter = "#{k}="
|
58
|
+
writer.send(setter, v) if writer.respond_to?(setter)
|
59
|
+
end
|
60
|
+
|
61
|
+
writer
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def run
|
66
|
+
rate_sources.each do | src |
|
67
|
+
# Create a historical rate writer.
|
68
|
+
initialize_writer
|
69
|
+
|
70
|
+
# Handle creating a summary rates called 'summary'.
|
71
|
+
if src == summary_rate_src
|
72
|
+
summary_rates(src)
|
73
|
+
else
|
74
|
+
require "currency/exchange/rate/source/#{src}"
|
75
|
+
src_cls_name = src.gsub(/(^|_)([a-z])/) { | m | $2.upcase }
|
76
|
+
src_cls = Currency::Exchange::Rate::Source.const_get(src_cls_name)
|
77
|
+
src = src_cls.new(source_options)
|
78
|
+
|
79
|
+
writer.source = src
|
80
|
+
|
81
|
+
writer.write_rates
|
82
|
+
end
|
83
|
+
end
|
84
|
+
ensure
|
85
|
+
@writer = nil
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def summary_rates(src)
|
90
|
+
# A list of summary rates.
|
91
|
+
summary_rates = [ ]
|
92
|
+
|
93
|
+
# Get a list of all rate time ranges before today,
|
94
|
+
# that do not have a 'cnu' summary rate.
|
95
|
+
h_rate_cls = Currency::Exchange::Rate::Source::Historical::Rate
|
96
|
+
conn = h_rate_cls.connection
|
97
|
+
|
98
|
+
# Select only rates from yesterday or before back till 30 days.
|
99
|
+
date_1 = Time.now - (0 * 24 * 60 * 60)
|
100
|
+
date_0 = date_1 - (30 * 24 * 60 * 60)
|
101
|
+
|
102
|
+
date_0 = conn.quote(date_0)
|
103
|
+
date_1 = conn.quote(date_1)
|
104
|
+
|
105
|
+
query =
|
106
|
+
"SELECT
|
107
|
+
DISTINCT a.date_0, a.date_1
|
108
|
+
FROM
|
109
|
+
#{h_rate_cls.table_name} AS a
|
110
|
+
WHERE
|
111
|
+
a.source <> '#{src}'
|
112
|
+
AND a.date_1 >= #{date_0} AND a.date_1 < #{date_1}
|
113
|
+
AND (SELECT COUNT(b.id) FROM #{h_rate_cls.table_name} AS b
|
114
|
+
WHERE
|
115
|
+
b.c1 = a.c1 AND b.c2 = a.c2
|
116
|
+
AND b.date_0 = a.date_0 AND b.date_1 = a.date_1
|
117
|
+
AND b.source = '#{src}') = 0
|
118
|
+
ORDER BY
|
119
|
+
date_0"
|
120
|
+
STDERR.puts "query = \n#{query.split("\n").join(' ')}" if verbose
|
121
|
+
|
122
|
+
dates = conn.query(query)
|
123
|
+
|
124
|
+
dates.each do | date_range |
|
125
|
+
STDERR.puts "\n=============================================\n" if verbose
|
126
|
+
STDERR.puts "date_range = #{date_range.inspect}" if verbose
|
127
|
+
|
128
|
+
# Query for all rates that have the same date range.
|
129
|
+
q_rate = h_rate_cls.new(:date_0 => date_range[0], :date_1 => date_range[1])
|
130
|
+
available_rates = q_rate.find_matching_this(:all)
|
131
|
+
|
132
|
+
# Collect all the currency pairs and rates.
|
133
|
+
currency_pair = { }
|
134
|
+
available_rates.each do | h_rate |
|
135
|
+
rate = h_rate.to_rate
|
136
|
+
(currency_pair[ [ rate.c1, rate.c2 ] ] ||= [ ]) << [ h_rate, rate ]
|
137
|
+
# STDERR.puts "rate = #{rate} #{h_rate.date_0} #{h_rate.date_1}" if verbose
|
138
|
+
end
|
139
|
+
|
140
|
+
currency_pair.each_pair do | currency_pair, rates |
|
141
|
+
STDERR.puts "\n =============================================\n" if verbose
|
142
|
+
STDERR.puts " currency_pair = #{currency_pair}" if verbose
|
143
|
+
|
144
|
+
# Create a summary rate for the currency pair.
|
145
|
+
selected_rates = [ ]
|
146
|
+
|
147
|
+
rates.each do | h_rates |
|
148
|
+
h_rate, rate = *h_rates
|
149
|
+
|
150
|
+
# Sanity check!
|
151
|
+
next if h_rate.source == src
|
152
|
+
|
153
|
+
# Found perferred source?
|
154
|
+
if h_rate.source == preferred_summary_source
|
155
|
+
selected_rates = [ h_rates ]
|
156
|
+
break
|
157
|
+
end
|
158
|
+
|
159
|
+
selected_rates << h_rates
|
160
|
+
end
|
161
|
+
|
162
|
+
unless selected_rates.empty?
|
163
|
+
summary_rate = Currency::Exchange::Rate::Writable.new(currency_pair[0], currency_pair[1], 0.0)
|
164
|
+
selected_rates.each do | h_rates |
|
165
|
+
h_rate, rate = *h_rates
|
166
|
+
STDERR.puts " rate = #{rate.inspect}" if verbose
|
167
|
+
summary_rate.collect_rate(rate)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Save the rate.
|
171
|
+
summary_rate.rate = summary_rate.rate_avg
|
172
|
+
summary_rate.source = src
|
173
|
+
summary_rate.derived = 'summary(' + selected_rates.collect{|r| r[0].id}.sort.join(',') + ')'
|
174
|
+
STDERR.puts " summary_rate = #{summary_rate} #{summary_rate.rate_samples}" if verbose
|
175
|
+
|
176
|
+
summary_rates << summary_rate
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
writer.write_rates(summary_rates)
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
|
@@ -78,7 +78,12 @@ class Currency::Exchange::Rate::Source::Historical::Writer
|
|
78
78
|
|
79
79
|
self.required_currencies.each do | c |
|
80
80
|
unless currencies.include?(c)
|
81
|
-
raise
|
81
|
+
raise ::Currency::Exception::MissingCurrency,
|
82
|
+
[
|
83
|
+
"Required currency #{c.inspect} not in #{currencies.inspect}",
|
84
|
+
:currency, c,
|
85
|
+
:required_currency, currencies,
|
86
|
+
]
|
82
87
|
end
|
83
88
|
end
|
84
89
|
end
|
@@ -194,7 +199,11 @@ class Currency::Exchange::Rate::Source::Historical::Writer
|
|
194
199
|
begin
|
195
200
|
rr.save!
|
196
201
|
rescue Object => err
|
197
|
-
raise
|
202
|
+
raise ::Currency::Exception::Generic,
|
203
|
+
[
|
204
|
+
"During save of #{rr.inspect}",
|
205
|
+
:error, err,
|
206
|
+
]
|
198
207
|
end
|
199
208
|
stored_h_rates << rr # Written.
|
200
209
|
end
|
@@ -103,7 +103,11 @@ class Currency::Exchange::Rate::Source::NewYorkFed < ::Currency::Exchange::Rate:
|
|
103
103
|
end
|
104
104
|
|
105
105
|
# $stderr.puts "rates = #{rates.inspect}"
|
106
|
-
raise ::Currency::Exception::UnavailableRates,
|
106
|
+
raise ::Currency::Exception::UnavailableRates,
|
107
|
+
[
|
108
|
+
"No rates found in #{get_uri.inspect}",
|
109
|
+
:uri, get_uri,
|
110
|
+
] if rates.empty?
|
107
111
|
|
108
112
|
rates
|
109
113
|
end
|
@@ -90,7 +90,7 @@ class Currency::Exchange::Rate::Source::Provider < Currency::Exchange::Rate::Sou
|
|
90
90
|
#
|
91
91
|
# Subclasses must define this method.
|
92
92
|
def load_rates(time = nil)
|
93
|
-
raise
|
93
|
+
raise Currency::Exception::SubclassResponsibility, :load_rates
|
94
94
|
end
|
95
95
|
|
96
96
|
|
@@ -159,7 +159,7 @@ class Currency::Exchange::Rate::Source::TimedCache < ::Currency::Exchange::Rate:
|
|
159
159
|
|
160
160
|
begin
|
161
161
|
# Do not allow re-entrancy
|
162
|
-
raise "Reentry!" if @processing_rates
|
162
|
+
raise Currency::Exception::InvalidReentrancy, "Reentry!" if @processing_rates
|
163
163
|
|
164
164
|
# Begin processing new rate request.
|
165
165
|
@processing_rates = true
|
@@ -83,8 +83,16 @@ class Currency::Exchange::Rate::Source::Xe < ::Currency::Exchange::Rate::Source:
|
|
83
83
|
rate = { }
|
84
84
|
cur_i = -1
|
85
85
|
eat_lines_until /^\s*<\/tr>/i do
|
86
|
-
|
87
|
-
|
86
|
+
# Grok:
|
87
|
+
#
|
88
|
+
# <td align="center" class="cur2 currencyA">114.676</td>\n
|
89
|
+
#
|
90
|
+
# AND
|
91
|
+
#
|
92
|
+
# <td align="center" class="cur2 currencyA"><div id="positionImage">0.9502\n
|
93
|
+
#
|
94
|
+
if md = /<td[^>]+?>\s*(<div[^>]+?>\s*)?(\d+\.\d+)\s*(<\/td>)?/i.match(@line)
|
95
|
+
usd_to_cur = md[2].to_f
|
88
96
|
cur_i = cur_i + 1
|
89
97
|
cur = currency[cur_i]
|
90
98
|
raise ParserError, "Currency not found at column #{cur_i}" unless cur
|
@@ -96,8 +104,15 @@ class Currency::Exchange::Rate::Source::Xe < ::Currency::Exchange::Rate::Source:
|
|
96
104
|
end
|
97
105
|
|
98
106
|
raise ::Currency::Exception::UnavailableRates, "No rates found in #{get_uri.inspect}" if rate.keys.empty?
|
99
|
-
|
100
|
-
|
107
|
+
|
108
|
+
raise ParserError,
|
109
|
+
[
|
110
|
+
"Not all currencies found",
|
111
|
+
:expected_currences, currency,
|
112
|
+
:found_currencies, rate.keys,
|
113
|
+
:missing_currencies, currency - rate.keys,
|
114
|
+
] if rate.keys.size != currency.size
|
115
|
+
|
101
116
|
@lines = @line = nil
|
102
117
|
|
103
118
|
raise ParserError, "Rate date not found" unless @rate_timestamp
|
@@ -115,7 +130,9 @@ class Currency::Exchange::Rate::Source::Xe < ::Currency::Exchange::Rate::Source:
|
|
115
130
|
end
|
116
131
|
yield @line if block_given?
|
117
132
|
end
|
118
|
-
|
133
|
+
|
134
|
+
raise ParserError, [ 'eat_lines_until failed', :rx, rx ]
|
135
|
+
|
119
136
|
false
|
120
137
|
end
|
121
138
|
|
@@ -129,7 +146,11 @@ class Currency::Exchange::Rate::Source::Xe < ::Currency::Exchange::Rate::Source:
|
|
129
146
|
|
130
147
|
rates = raw_rates # Load rates
|
131
148
|
rates_pivot = rates[PIVOT_CURRENCY]
|
132
|
-
raise ::Currency::Exception::UnknownRate
|
149
|
+
raise ::Currency::Exception::UnknownRate,
|
150
|
+
[
|
151
|
+
"Cannot get base rate #{PIVOT_CURRENCY.inspect}",
|
152
|
+
:pivot_currency, PIVOT_CURRENCY,
|
153
|
+
] unless rates_pivot
|
133
154
|
|
134
155
|
result = rates_pivot.keys.collect do | c2 |
|
135
156
|
new_rate(PIVOT_CURRENCY, c2, rates_pivot[c2], @rate_timestamp)
|
data/lib/currency/macro.rb
CHANGED
@@ -190,7 +190,7 @@ end_eval
|
|
190
190
|
when :integer
|
191
191
|
to_rep = 'to_i'
|
192
192
|
else
|
193
|
-
|
193
|
+
raise ::Currency::Exception::InvalidMoneyValue, "Cannot use value representation: #{rep.inspect}"
|
194
194
|
end
|
195
195
|
from_rep = '::Currency::Money.new'
|
196
196
|
end
|
@@ -293,7 +293,7 @@ def #{attr_name}=(value)
|
|
293
293
|
#{write_preferred_currency}
|
294
294
|
#{convert_currency}
|
295
295
|
else
|
296
|
-
|
296
|
+
raise ::Currency::Exception::InvalidMoneyValue, value
|
297
297
|
end
|
298
298
|
|
299
299
|
@#{attr_name} = #{attr_name}_money
|
data/lib/currency/money.rb
CHANGED
@@ -272,7 +272,7 @@ class Currency::Money
|
|
272
272
|
# Attempt conversion?
|
273
273
|
if @currency != currency || (time && @time != time)
|
274
274
|
self.convert(currency, time).rep
|
275
|
-
# raise
|
275
|
+
# raise ::Currency::Exception::Generic, "Incompatible Currency: #{@currency} != #{currency}"
|
276
276
|
else
|
277
277
|
@rep
|
278
278
|
end
|
data/lib/currency/parser.rb
CHANGED
@@ -60,7 +60,13 @@ class Currency::Parser
|
|
60
60
|
if (md = /(\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?Z)/.match(x))
|
61
61
|
time = Time.xmlschema(md[1])
|
62
62
|
unless time
|
63
|
-
raise
|
63
|
+
raise Currency::Exception::InvalidMoneyString,
|
64
|
+
[
|
65
|
+
"time: #{str.inspect} #{currency} #{x.inspect}",
|
66
|
+
:string => str,
|
67
|
+
:currency => currency,
|
68
|
+
:state => x,
|
69
|
+
]
|
64
70
|
end
|
65
71
|
x = md.pre_match + md.post_match
|
66
72
|
end
|
@@ -76,7 +82,13 @@ class Currency::Parser
|
|
76
82
|
x = md.pre_match + md.post_match
|
77
83
|
if @currency && @currency != curr
|
78
84
|
if @enforce_currency
|
79
|
-
raise ::Currency::Exception::IncompatibleCurrency
|
85
|
+
raise ::Currency::Exception::IncompatibleCurrency,
|
86
|
+
[
|
87
|
+
"currency: #{str.inspect} #{@currency.code}",
|
88
|
+
:string, str,
|
89
|
+
:default_currency, @currency,
|
90
|
+
:parsed_currency, curr,
|
91
|
+
]
|
80
92
|
end
|
81
93
|
convert_currency = @currency
|
82
94
|
end
|
@@ -129,7 +141,13 @@ class Currency::Parser
|
|
129
141
|
else
|
130
142
|
# $stderr.puts "'#{self}'.parse(#{str}) => ??? '#{x}'"
|
131
143
|
#x.to_f.Money_rep(self)
|
132
|
-
raise ::Currency::Exception::InvalidMoneyString
|
144
|
+
raise ::Currency::Exception::InvalidMoneyString,
|
145
|
+
[
|
146
|
+
"#{str.inspect} #{currency} #{x.inspect}",
|
147
|
+
:string => str,
|
148
|
+
:currency => currency,
|
149
|
+
:state => x,
|
150
|
+
]
|
133
151
|
end
|
134
152
|
|
135
153
|
# Do conversion.
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.
|
2
|
+
rubygems_version: 0.9.4
|
3
3
|
specification_version: 1
|
4
4
|
name: currency
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.4.
|
7
|
-
date: 2007-
|
6
|
+
version: 0.4.9
|
7
|
+
date: 2007-11-01 00:00:00 -05:00
|
8
8
|
summary: "Currency models currencies, monetary values, foreign exchanges rates. Pulls live and historical rates from http://xe.com/, http://newyorkfed.org/, http://thefinancials.com/. Can store/retrieve historical rate data from database using ActiveRecord. Can store/retrieve Money values using ActiveRecord. For more details, see: http://rubyforge.org/projects/currency/ http://currency.rubyforge.org/ http://currency.rubyforge.org/files/README_txt.html"
|
9
9
|
require_paths:
|
10
10
|
- lib
|
11
|
-
- test
|
12
11
|
email: ruby-currency@umleta.com
|
13
12
|
homepage: http://rubyforge.org/projects/currency
|
14
13
|
rubyforge_project: currency
|
@@ -38,6 +37,7 @@ files:
|
|
38
37
|
- Rakefile
|
39
38
|
- Releases.txt
|
40
39
|
- TODO.txt
|
40
|
+
- bin/currency_historical_rate_load
|
41
41
|
- examples/ex1.rb
|
42
42
|
- examples/xe1.rb
|
43
43
|
- lib/currency.rb
|
@@ -57,6 +57,7 @@ files:
|
|
57
57
|
- lib/currency/exchange/rate/source/federal_reserve.rb
|
58
58
|
- lib/currency/exchange/rate/source/historical.rb
|
59
59
|
- lib/currency/exchange/rate/source/historical/rate.rb
|
60
|
+
- lib/currency/exchange/rate/source/historical/rate_loader.rb
|
60
61
|
- lib/currency/exchange/rate/source/historical/writer.rb
|
61
62
|
- lib/currency/exchange/rate/source/new_york_fed.rb
|
62
63
|
- lib/currency/exchange/rate/source/provider.rb
|
@@ -86,14 +87,35 @@ files:
|
|
86
87
|
- test/time_quantitizer_test.rb
|
87
88
|
- test/timed_cache_test.rb
|
88
89
|
- test/xe_test.rb
|
89
|
-
test_files:
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
90
|
+
test_files:
|
91
|
+
- test/config_test.rb
|
92
|
+
- test/test_base.rb
|
93
|
+
- test/parser_test.rb
|
94
|
+
- test/ar_test_core.rb
|
95
|
+
- test/ar_test_base.rb
|
96
|
+
- test/ar_simple_test.rb
|
97
|
+
- test/formatter_test.rb
|
98
|
+
- test/xe_test.rb
|
99
|
+
- test/money_test.rb
|
100
|
+
- test/timed_cache_test.rb
|
101
|
+
- test/historical_writer_test.rb
|
102
|
+
- test/ar_column_test.rb
|
103
|
+
- test/macro_test.rb
|
104
|
+
- test/federal_reserve_test.rb
|
105
|
+
- test/new_york_fed_test.rb
|
106
|
+
- test/time_quantitizer_test.rb
|
107
|
+
rdoc_options:
|
108
|
+
- --main
|
109
|
+
- README.txt
|
110
|
+
extra_rdoc_files:
|
111
|
+
- COPYING.txt
|
112
|
+
- LICENSE.txt
|
113
|
+
- Manifest.txt
|
114
|
+
- README.txt
|
115
|
+
- Releases.txt
|
116
|
+
- TODO.txt
|
117
|
+
executables:
|
118
|
+
- currency_historical_rate_load
|
97
119
|
extensions: []
|
98
120
|
|
99
121
|
requirements: []
|
@@ -106,5 +128,5 @@ dependencies:
|
|
106
128
|
requirements:
|
107
129
|
- - ">="
|
108
130
|
- !ruby/object:Gem::Version
|
109
|
-
version: 1.
|
131
|
+
version: 1.3.0
|
110
132
|
version:
|