mumboe-currency 0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/bin/currency_historical_rate_load +105 -0
  2. data/examples/ex1.rb +13 -0
  3. data/examples/xe1.rb +20 -0
  4. data/lib/currency/active_record.rb +265 -0
  5. data/lib/currency/config.rb +91 -0
  6. data/lib/currency/core_extensions.rb +41 -0
  7. data/lib/currency/currency/factory.rb +228 -0
  8. data/lib/currency/currency.rb +175 -0
  9. data/lib/currency/currency_version.rb +6 -0
  10. data/lib/currency/exception.rb +119 -0
  11. data/lib/currency/exchange/rate/deriver.rb +157 -0
  12. data/lib/currency/exchange/rate/source/base.rb +166 -0
  13. data/lib/currency/exchange/rate/source/failover.rb +63 -0
  14. data/lib/currency/exchange/rate/source/federal_reserve.rb +160 -0
  15. data/lib/currency/exchange/rate/source/historical/rate.rb +184 -0
  16. data/lib/currency/exchange/rate/source/historical/rate_loader.rb +186 -0
  17. data/lib/currency/exchange/rate/source/historical/writer.rb +220 -0
  18. data/lib/currency/exchange/rate/source/historical.rb +79 -0
  19. data/lib/currency/exchange/rate/source/new_york_fed.rb +127 -0
  20. data/lib/currency/exchange/rate/source/provider.rb +120 -0
  21. data/lib/currency/exchange/rate/source/test.rb +50 -0
  22. data/lib/currency/exchange/rate/source/the_financials.rb +191 -0
  23. data/lib/currency/exchange/rate/source/timed_cache.rb +198 -0
  24. data/lib/currency/exchange/rate/source/xe.rb +165 -0
  25. data/lib/currency/exchange/rate/source.rb +89 -0
  26. data/lib/currency/exchange/rate.rb +214 -0
  27. data/lib/currency/exchange/time_quantitizer.rb +111 -0
  28. data/lib/currency/exchange.rb +50 -0
  29. data/lib/currency/formatter.rb +290 -0
  30. data/lib/currency/macro.rb +321 -0
  31. data/lib/currency/money.rb +295 -0
  32. data/lib/currency/money_helper.rb +13 -0
  33. data/lib/currency/parser.rb +151 -0
  34. data/lib/currency.rb +143 -0
  35. data/test/string_test.rb +54 -0
  36. data/test/test_base.rb +44 -0
  37. metadata +90 -0
@@ -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
+
data/examples/ex1.rb ADDED
@@ -0,0 +1,13 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'currency'
4
+ require 'currency/exchange/rate/source/test'
5
+
6
+ x = Currency.Money("1,203.43", :USD)
7
+
8
+ puts x.to_s
9
+ puts (x * 10).to_s
10
+ puts (x * 33333).inspect
11
+
12
+ puts x.currency.code.inspect
13
+
data/examples/xe1.rb ADDED
@@ -0,0 +1,20 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'currency'
4
+ require 'currency/exchange/rate/source/xe'
5
+
6
+ ex = Currency::Exchange::Rate::Source::Xe.new()
7
+ Currency::Exchange::Rate::Source.current = ex
8
+
9
+ puts ex.inspect
10
+ puts ex.parse_page_rates.inspect
11
+
12
+ usd = Currency.Money("1", 'USD')
13
+
14
+ puts "usd = #{usd}"
15
+
16
+ cad = usd.convert(:CAD)
17
+ puts "cad = #{cad}"
18
+
19
+
20
+
@@ -0,0 +1,265 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+ require 'active_record/base'
5
+ require File.join(File.dirname(__FILE__), '..', 'currency')
6
+
7
+ # See Currency::ActiveRecord::ClassMethods
8
+ class ActiveRecord::Base
9
+ @@money_attributes = { }
10
+
11
+ # Called by money macro when a money attribute
12
+ # is created.
13
+ def self.register_money_attribute(attr_opts)
14
+ (@@money_attributes[attr_opts[:class]] ||= { })[attr_opts[:attr_name]] = attr_opts
15
+ end
16
+
17
+ # Returns an array of option hashes for all the money attributes of
18
+ # this class.
19
+ #
20
+ # Superclass attributes are not included.
21
+ def self.money_attributes_for_class(cls)
22
+ (@@money_atttributes[cls] || { }).values
23
+ end
24
+
25
+
26
+ # Iterates through all known money attributes in all classes.
27
+ #
28
+ # each_money_attribute { | money_opts |
29
+ # ...
30
+ # }
31
+ def self.each_money_attribute(&blk)
32
+ @@money_attributes.each do | cls, hash |
33
+ hash.each do | attr_name, attr_opts |
34
+ yield attr_opts
35
+ end
36
+ end
37
+ end
38
+
39
+ end # class
40
+
41
+
42
+ # See Currency::ActiveRecord::ClassMethods
43
+ module Currency::ActiveRecord
44
+
45
+ def self.append_features(base) # :nodoc:
46
+ # $stderr.puts " Currency::ActiveRecord#append_features(#{base})"
47
+ super
48
+ base.extend(ClassMethods)
49
+ end
50
+
51
+
52
+
53
+ # == ActiveRecord Suppport
54
+ #
55
+ # Support for Money attributes in ActiveRecord::Base subclasses:
56
+ #
57
+ # require 'currency'
58
+ # require 'currency/active_record'
59
+ #
60
+ # class Entry < ActiveRecord::Base
61
+ # attr_money :amount
62
+ # end
63
+ #
64
+ module ClassMethods
65
+
66
+ # Deprecated: use attr_money.
67
+ def money(*args)
68
+ $stderr.puts "WARNING: money(#{args.inspect}) deprecated, use attr_money: in #{caller(1)[0]}"
69
+ attr_money(*args)
70
+ end
71
+
72
+
73
+ # Defines a Money object attribute that is bound
74
+ # to a database column. The database column to store the
75
+ # Money value representation is assumed to be
76
+ # INTEGER and will store Money#rep values.
77
+ #
78
+ # Options:
79
+ #
80
+ # :column => undef
81
+ #
82
+ # Defines the column to use for storing the money value.
83
+ # Defaults to the attribute name.
84
+ #
85
+ # If this column is different from the attribute name,
86
+ # the money object will intercept column=(x) to flush
87
+ # any cached Money object.
88
+ #
89
+ # :currency => currency_code (e.g.: :USD)
90
+ #
91
+ # Defines the Currency to use for storing a normalized Money
92
+ # value.
93
+ #
94
+ # All Money values will be converted to this Currency before
95
+ # storing. This allows SQL summary operations,
96
+ # like SUM(), MAX(), AVG(), etc., to produce meaningful results,
97
+ # regardless of the initial currency specified. If this
98
+ # option is used, subsequent reads will be in the specified
99
+ # normalization :currency.
100
+ #
101
+ # :currency_column => undef
102
+ #
103
+ # Defines the name of the CHAR(3) column used to store and
104
+ # retrieve the Money's Currency code. If this option is used, each
105
+ # record may use a different Currency to store the result, such
106
+ # that SQL summary operations, like SUM(), MAX(), AVG(),
107
+ # may return meaningless results.
108
+ #
109
+ # :currency_preferred_column => undef
110
+ #
111
+ # Defines the name of a CHAR(3) column used to store and
112
+ # retrieve the Money's Currency code. This option can be used
113
+ # with normalized Money values to retrieve the Money value
114
+ # in its original Currency, while
115
+ # allowing SQL summary operations on the normalized Money values
116
+ # to still be valid.
117
+ #
118
+ # :time => undef
119
+ #
120
+ # Defines the name of attribute used to
121
+ # retrieve the Money's time. If this option is used, each
122
+ # Money value will use this attribute during historical Currency
123
+ # conversions.
124
+ #
125
+ # Money values can share a time value with other attributes
126
+ # (e.g. a created_on column).
127
+ #
128
+ # If this option is true, the money time attribute will be named
129
+ # "#{attr_name}_time" and :time_update will be true.
130
+ #
131
+ # :time_update => undef
132
+ #
133
+ # If true, the Money time value is updated upon setting the
134
+ # money attribute.
135
+ #
136
+ def attr_money(attr_name, *opts)
137
+ opts = Hash[*opts]
138
+
139
+ attr_name = attr_name.to_s
140
+ opts[:class] = self
141
+ opts[:table] = self.table_name
142
+ opts[:attr_name] = attr_name.intern
143
+ ::ActiveRecord::Base.register_money_attribute(opts)
144
+
145
+ column = opts[:column] || opts[:attr_name]
146
+ opts[:column] = column
147
+
148
+ if column.to_s != attr_name.to_s
149
+ alias_accessor = <<-"end_eval"
150
+ alias :before_money_#{column}=, :#{column}=
151
+
152
+ def #{column}=(__value)
153
+ @{attr_name} = nil # uncache
154
+ before_money#{column} = __value
155
+ end
156
+
157
+ end_eval
158
+ end
159
+
160
+ currency = opts[:currency]
161
+
162
+ currency_column = opts[:currency_column]
163
+ if currency_column && ! currency_column.kind_of?(String)
164
+ currency_column = currency_column.to_s
165
+ currency_column = "#{column}_currency"
166
+ end
167
+ if currency_column
168
+ read_currency = "read_attribute(:#{currency_column.to_s})"
169
+ write_currency = "write_attribute(:#{currency_column}, #{attr_name}_money.nil? ? nil : #{attr_name}_money.currency.code.to_s)"
170
+ end
171
+ opts[:currency_column] = currency_column
172
+
173
+ currency_preferred_column = opts[:currency_preferred_column]
174
+ if currency_preferred_column
175
+ currency_preferred_column = currency_preferred_column.to_s
176
+ read_preferred_currency = "@#{attr_name} = @#{attr_name}.convert(read_attribute(:#{currency_preferred_column}))"
177
+ write_preferred_currency = "write_attribute(:#{currency_preferred_column}, @#{attr_name}_money.currency.code)"
178
+ end
179
+
180
+ time = opts[:time]
181
+ write_time = ''
182
+ if time
183
+ if time == true
184
+ time = "#{attr_name}_time"
185
+ opts[:time_update] = true
186
+ end
187
+ read_time = "self.#{time}"
188
+ end
189
+ opts[:time] = time
190
+ if opts[:time_update]
191
+ write_time = "self.#{time} = #{attr_name}_money && #{attr_name}_money.time"
192
+ end
193
+
194
+ currency ||= ':USD'
195
+ time ||= 'nil'
196
+
197
+ read_currency ||= currency
198
+ read_time ||= time
199
+
200
+ money_rep ||= "#{attr_name}_money.rep"
201
+
202
+ validate_allow_nil = opts[:allow_nil] ? ', :allow_nil => true' : ''
203
+ validate = "# Validation\n"
204
+ validate << "\nvalidates_numericality_of :#{attr_name}#{validate_allow_nil}\n"
205
+ validate << "\nvalidates_format_of :#{currency_column}, :with => /^[A-Z][A-Z][A-Z]$/#{validate_allow_nil}\n" if currency_column
206
+
207
+
208
+ alias_accessor ||= ''
209
+
210
+ module_eval (opts[:module_eval] = x = <<-"end_eval"), __FILE__, __LINE__
211
+ #{validate}
212
+
213
+ #{alias_accessor}
214
+
215
+ def #{attr_name}
216
+ # $stderr.puts " \#{self.class.name}##{attr_name}"
217
+ unless @#{attr_name}
218
+ #{attr_name}_rep = read_attribute(:#{column})
219
+ unless #{attr_name}_rep.nil?
220
+ @#{attr_name} = ::Currency::Money.new_rep(#{attr_name}_rep, #{read_currency} || #{currency}, #{read_time} || #{time})
221
+ #{read_preferred_currency}
222
+ end
223
+ end
224
+ @#{attr_name}
225
+ end
226
+
227
+ def #{attr_name}=(value)
228
+ if value.nil?
229
+ #{attr_name}_money = nil
230
+ elsif value.kind_of?(Integer) || value.kind_of?(String) || value.kind_of?(Float)
231
+ #{attr_name}_money = ::Currency::Money(value, #{currency})
232
+ #{write_preferred_currency}
233
+ elsif value.kind_of?(::Currency::Money)
234
+ #{attr_name}_money = value
235
+ #{write_preferred_currency}
236
+ #{write_currency ? write_currency : "#{attr_name}_money = #{attr_name}_money.convert(#{currency})"}
237
+ else
238
+ raise ::Currency::Exception::InvalidMoneyValue, value
239
+ end
240
+
241
+ @#{attr_name} = #{attr_name}_money
242
+
243
+ write_attribute(:#{column}, #{attr_name}_money.nil? ? nil : #{money_rep})
244
+ #{write_time}
245
+
246
+ value
247
+ end
248
+
249
+ def #{attr_name}_before_type_cast
250
+ # FIXME: User cannot specify Currency
251
+ x = #{attr_name}
252
+ x &&= x.format(:symbol => false, :currency => false, :thousands => false)
253
+ x
254
+ end
255
+
256
+ end_eval
257
+ # $stderr.puts " CODE = #{x}"
258
+ end
259
+ end
260
+ end
261
+
262
+
263
+ ActiveRecord::Base.class_eval do
264
+ include Currency::ActiveRecord
265
+ end
@@ -0,0 +1,91 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+ # The Currency::Config class is responsible for
5
+ # maintaining global configuration for the Currency package.
6
+ #
7
+ # TO DO:
8
+ #
9
+ # Migrate all class variable configurations to this object.
10
+ class Currency::Config
11
+ @@default = nil
12
+
13
+ # Returns the default Currency::Config object.
14
+ #
15
+ # If one is not specfied an instance is
16
+ # created. This is a global, not thread-local.
17
+ def self.default
18
+ @@default ||=
19
+ self.new
20
+ end
21
+
22
+ # Sets the default Currency::Config object.
23
+ def self.default=(x)
24
+ @@default = x
25
+ end
26
+
27
+ # Returns the current Currency::Config object used during
28
+ # in the current thread.
29
+ #
30
+ # If #current= has not been called and #default= has not been called,
31
+ # then UndefinedExchange is raised.
32
+ def self.current
33
+ Thread.current[:Currency__Config] ||=
34
+ self.default ||
35
+ (raise ::Currency::Exception::UndefinedConfig, "Currency::Config.default not defined")
36
+ end
37
+
38
+ # Sets the current Currency::Config object used
39
+ # in the current thread.
40
+ def self.current=(x)
41
+ Thread.current[:Currency__Config] = x
42
+ end
43
+
44
+ # Clones the current configuration and makes it current
45
+ # during the execution of a block. After block completes,
46
+ # the previous configuration is restored.
47
+ #
48
+ # Currency::Config.configure do | c |
49
+ # c.float_ref_filter = Proc.new { | x | x.round }
50
+ # "123.448".money.rep == 12345
51
+ # end
52
+ def self.configure(&blk)
53
+ c_prev = current
54
+ c_new = self.current = current.clone
55
+ result = nil
56
+ begin
57
+ result = yield c_new
58
+ ensure
59
+ self.current = c_prev
60
+ end
61
+ result
62
+ end
63
+
64
+
65
+ @@identity = Proc.new { |x| x } # :nodoc:
66
+
67
+ # Returns the current Float conversion filter.
68
+ # Can be used to set rounding or truncation policies when converting
69
+ # Float values to Money values.
70
+ # Defaults to an identity function.
71
+ # See Float#Money_rep.
72
+ def float_ref_filter
73
+ @float_ref_filter ||=
74
+ @@identity
75
+ end
76
+
77
+ # Sets the current Float conversion filter.
78
+ def float_ref_filter=(x)
79
+ @float_ref_filter = x
80
+ end
81
+
82
+
83
+ # Defines the table name for Historical::Rate records.
84
+ # Defaults to 'currency_historical_rates'.
85
+ attr_accessor :historical_table_name
86
+ def historical_table_name
87
+ @historical_table_name ||= 'currency_historical_rates'
88
+ end
89
+
90
+ end # module
91
+
@@ -0,0 +1,41 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+
5
+
6
+ class Object
7
+ # Exact conversion to Money representation value.
8
+ def money(*opts)
9
+ Currency::Money(self, *opts)
10
+ end
11
+ end
12
+
13
+
14
+
15
+ class Integer
16
+ # Exact conversion to Money representation value.
17
+ def Money_rep(currency, time = nil)
18
+ Integer(self * currency.scale)
19
+ end
20
+ end
21
+
22
+
23
+
24
+ class Float
25
+ # Inexact conversion to Money representation value.
26
+ def Money_rep(currency, time = nil)
27
+ Integer(Currency::Config.current.float_ref_filter.call(self * currency.scale))
28
+ end
29
+ end
30
+
31
+
32
+
33
+ class String
34
+ # Exact conversion to Money representation value.
35
+ def Money_rep(currency, time = nil)
36
+ x = currency.parse(self, :currency => currency, :time => time)
37
+ x = x.rep if x.respond_to?(:rep)
38
+ x
39
+ end
40
+ end
41
+