mumboe-currency 0.5

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.
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
+