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.
@@ -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
@@ -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
- throw ::Currency::Exception::InvalidMoneyValue.new(value)
238
+ raise ::Currency::Exception::InvalidMoneyValue, value
239
239
  end
240
240
 
241
241
  @#{attr_name} = #{attr_name}_money
@@ -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.new("Currency::Config.default not defined"))
35
+ (raise ::Currency::Exception::UndefinedConfig, "Currency::Config.default not defined")
36
36
  end
37
37
 
38
38
  # Sets the current Currency::Config object used
@@ -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.new(x) unless x.kind_of?(Symbol)
73
- raise ::Currency::Exception::InvalidCurrencyCode.new(x) unless x.to_s.length == 3
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.new() unless currency
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
@@ -1,5 +1,5 @@
1
1
  module Currency
2
- CurrencyVersion = '0.4.7'
2
+ CurrencyVersion = '0.4.9'
3
3
  end
4
4
  # DO NOT EDIT
5
5
  # This file is auto-generated by build scripts.
@@ -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
@@ -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("UNIMPLEMENTED")
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.new("Currency::Exchange.current not defined"))
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.new(@rate) unless @rate && @rate >= 0.0
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("Cannot collect rates between different currency pairs")
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.new("Currency::Exchange.current not defined"))
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
- raise ::Currency::Exception::SubclassResponsibility, "#{self.class}#rate"
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
- raise ::Currency::Exception::SubclassResponsibility, "#{self.class}#get_rate"
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
- raise ::Currency::Exception::SubclassResponsibility, "#{self.class}#get_rate_base"
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
- raise ::Currency::Exception::SubclassResponsibility, "#{self.class}#get_rate"
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.put "Failover: primary failed for get_rate(#{c1}, #{c2}, #{time}) : #{err.inspect}"
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("Failover: secondary failed for get_rate(#{c1}, #{c2}, #{time})")
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(::Currency::Exception::UnavailableRates, "Cannot determine currency code for federalreserve.gov country code #{country_code.inspect}")
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} #{date}" if @verbose
125
+ STDERR.puts "#{c1} #{c2} #{rate}\t#{date}" if @verbose
126
126
 
127
127
  rates << new_rate(c1, c2, rate, date)
128
128
 
@@ -1,3 +1,4 @@
1
+ require 'active_support'
1
2
  require 'active_record/base'
2
3
 
3
4
  require 'currency/exchange/rate/source/historical'
@@ -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("Required currency #{c.inspect} not in #{currencies.inspect}")
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 Error, "During save of #{rr.inspect} : #{err.inspect}"
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, "No rates found in #{get_uri.inspect}" if rates.empty?
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('Subclass responsiblity')
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
- if md = /<td[^>]+?>\s*?(\d+\.\d+)\s*?<\/td>/i.match(@line)
87
- usd_to_cur = md[1].to_f
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
- raise ParserError, "Not all rates found" if rate.keys.size != currency.size
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
- raise ParserError, rx.inspect
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.new("#{self}: cannot get base rate #{PIVOT_CURRENCY.inspect}") unless rates_pivot
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)
@@ -190,7 +190,7 @@ end_eval
190
190
  when :integer
191
191
  to_rep = 'to_i'
192
192
  else
193
- throw ::Currency::Exception::InvalidMoneyValue.new("Cannot use value representation: #{rep.inspect}")
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
- throw ::Currency::Exception::InvalidMoneyValue.new(value)
296
+ raise ::Currency::Exception::InvalidMoneyValue, value
297
297
  end
298
298
 
299
299
  @#{attr_name} = #{attr_name}_money
@@ -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("Incompatible Currency: #{@currency} != #{currency}")
275
+ # raise ::Currency::Exception::Generic, "Incompatible Currency: #{@currency} != #{currency}"
276
276
  else
277
277
  @rep
278
278
  end
@@ -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 ::Currency::Exception::InvalidMoneyString.new("time: #{str.inspect} #{currency} #{x.inspect}")
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.new("currency: #{str.inspect} #{@currency.code}")
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.new("#{str.inspect} #{currency} #{x.inspect}")
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.0
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
7
- date: 2007-06-25 00:00:00 -04:00
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
- rdoc_options: []
92
-
93
- extra_rdoc_files: []
94
-
95
- executables: []
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.1.2
131
+ version: 1.3.0
110
132
  version: