currency 0.4.7 → 0.4.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: