currency 0.4.7 → 0.4.9
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.
- 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:
|