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,290 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+ require 'rss/rss' # Time#xmlschema
5
+
6
+
7
+ # This class formats a Money value as a String.
8
+ # Each Currency has a default Formatter.
9
+ class Currency::Formatter
10
+ # The underlying object for Currency::Formatter#format.
11
+ # This object is cloned and initialized with strings created
12
+ # from Formatter#format.
13
+ # It handles the Formatter#format string interpolation.
14
+ class Template
15
+ @@empty_hash = { }
16
+ @@empty_hash.freeze
17
+
18
+ # The template string.
19
+ attr_accessor :template
20
+
21
+ # The Currency::Money object being formatted.
22
+ attr_accessor :money
23
+ # The Currency::Currency object being formatted.
24
+ attr_accessor :currency
25
+
26
+ # The sign: '-' or nil.
27
+ attr_accessor :sign
28
+ # The whole part of the value, with thousands_separator or nil.
29
+ attr_accessor :whole
30
+ # The fraction part of the value, with decimal_separator or nil.
31
+ attr_accessor :fraction
32
+ # The currency symbol or nil.
33
+ attr_accessor :symbol
34
+ # The currency code or nil.
35
+ attr_accessor :code
36
+
37
+ # The time or nil.
38
+ attr_accessor :time
39
+
40
+ def initialize(opts = @@empty_hash)
41
+ @template =
42
+ @template_proc =
43
+ nil
44
+
45
+ opts.each_pair{ | k, v | self.send("#{k}=", v) }
46
+ end
47
+
48
+
49
+ # Sets the template string and uncaches the template_proc.
50
+ def template=(x)
51
+ if @template != x
52
+ @template_proc = nil
53
+ end
54
+ @template = x
55
+ end
56
+
57
+
58
+ # Defines a the self._format template procedure using
59
+ # the template as a string to be interpolated.
60
+ def template_proc(template = @template)
61
+ return @template_proc if @template_proc
62
+ @template_proc = template || ''
63
+ # @template_proc = @template_proc.gsub(/[\\"']/) { | x | "\\" + x }
64
+ @template_proc = "def self._format; \"#{@template_proc}\"; end"
65
+ self.instance_eval @template_proc
66
+ @template_proc
67
+ end
68
+
69
+
70
+ # Formats the current state using the template.
71
+ def format
72
+ template_proc
73
+ _format
74
+ end
75
+ end
76
+
77
+
78
+ # Defaults to ','
79
+ attr_accessor :thousands_separator
80
+
81
+ # Defaults to '.'
82
+ attr_accessor :decimal_separator
83
+
84
+ # If true, insert _thousands_separator_ between each 3 digits in the whole value.
85
+ attr_accessor :thousands
86
+
87
+ # If true, append _decimal_separator_ and decimal digits after whole value.
88
+ attr_accessor :cents
89
+
90
+ # If true, prefix value with currency symbol.
91
+ attr_accessor :symbol
92
+
93
+ # If true, append currency code.
94
+ attr_accessor :code
95
+
96
+ # If true, append the time.
97
+ attr_accessor :time
98
+
99
+ # The number of fractional digits in the time.
100
+ # Defaults to 4.
101
+ attr_accessor :time_fractional_digits
102
+
103
+ # If true, use html formatting.
104
+ #
105
+ # Currency::Money(12.45, :EUR).to_s(:html => true; :code => true)
106
+ # => "&#8364;12.45 <span class=\"currency_code\">EUR</span>"
107
+ attr_accessor :html
108
+
109
+ # A template string used to format a money value.
110
+ # Defaults to:
111
+ #
112
+ # '#{code}#{code && " "}#{symbol}#{sign}#{whole}#{fraction}#{time && " "}#{time}'
113
+ attr_accessor :template
114
+
115
+
116
+ # If passed true, formats for an input field (i.e.: as a number).
117
+ def as_input_value=(x)
118
+ if x
119
+ self.thousands_separator = ''
120
+ self.decimal_separator = '.'
121
+ self.thousands = false
122
+ self.cents = true
123
+ self.symbol = false
124
+ self.code = false
125
+ self.html = false
126
+ self.time = false
127
+ self.time_fractional_digits = nil
128
+ end
129
+ x
130
+ end
131
+
132
+
133
+ @@default = nil
134
+ # Get the default Formatter.
135
+ def self.default
136
+ @@default || self.new
137
+ end
138
+
139
+
140
+ # Set the default Formatter.
141
+ def self.default=(x)
142
+ @@default = x
143
+ end
144
+
145
+
146
+ def initialize(opt = { })
147
+ @thousands_separator = ','
148
+ @decimal_separator = '.'
149
+ @thousands = true
150
+ @cents = true
151
+ @symbol = true
152
+ @code = false
153
+ @html = false
154
+ @time = false
155
+ @time_fractional_digits = 4
156
+ @template = '#{code}#{code && " "}#{symbol}#{sign}#{whole}#{fraction}#{time && " "}#{time}'
157
+ @template_object = nil
158
+
159
+ opt.each_pair{ | k, v | self.send("#{k}=", v) }
160
+ end
161
+
162
+
163
+ def currency=(x) # :nodoc:
164
+ # DO NOTHING!
165
+ end
166
+
167
+
168
+ # Sets the template and the Template#template.
169
+ def template=(x)
170
+ if @template_object
171
+ @template_object.template = x
172
+ end
173
+ @template = x
174
+ end
175
+
176
+
177
+ # Returns the Template object.
178
+ def template_object
179
+ return @template_object if @template_object
180
+
181
+ @template_object = Template.new
182
+ @template_object.template = @template if @template
183
+ # $stderr.puts "template.template = #{@template_object.template.inspect}"
184
+ @template_object.template_proc # pre-cache before clone.
185
+
186
+ @template_object
187
+ end
188
+
189
+
190
+ def _format(m, currency = nil, time = nil) # :nodoc:
191
+ # Get currency.
192
+ currency ||= m.currency
193
+
194
+ # Get time.
195
+ time ||= m.time
196
+
197
+ # Setup template
198
+ tmpl = self.template_object.clone
199
+ # $stderr.puts "template.template = #{tmpl.template.inspect}"
200
+ tmpl.money = m
201
+ tmpl.currency = currency
202
+
203
+ # Get scaled integer representation for this Currency.
204
+ # $stderr.puts "m.currency = #{m.currency}, currency => #{currency}"
205
+ x = m.Money_rep(currency)
206
+
207
+ # Remove sign.
208
+ x = - x if ( neg = x < 0 )
209
+ tmpl.sign = neg ? '-' : nil
210
+
211
+ # Convert to String.
212
+ x = x.to_s
213
+
214
+ # Keep prefixing "0" until filled to scale.
215
+ while ( x.length <= currency.scale_exp )
216
+ x = "0" + x
217
+ end
218
+
219
+ # Insert decimal place.
220
+ whole = x[0 .. currency.format_left]
221
+ fraction = x[currency.format_right .. -1]
222
+
223
+ # Do thousands.
224
+ x = whole
225
+ if @thousands && (@thousands_separator && ! @thousands_separator.empty?)
226
+ x.reverse!
227
+ x.gsub!(/(\d\d\d)/) {|y| y + @thousands_separator}
228
+ x.sub!(/#{@thousands_separator}$/,'')
229
+ x.reverse!
230
+ end
231
+
232
+ # Put whole and fractional parts.
233
+ tmpl.whole = x
234
+ tmpl.fraction = @cents && @decimal_separator ? @decimal_separator + fraction : nil
235
+
236
+
237
+ # Add symbol?
238
+ tmpl.symbol = @symbol ? ((@html && currency.symbol_html) || currency.symbol) : nil
239
+
240
+
241
+ # Add currency code.
242
+ tmpl.code = @code ? _format_Currency(currency) : nil
243
+
244
+ # Add time.
245
+ tmpl.time = @time && time ? _format_Time(time) : nil
246
+
247
+ # Ask template to format the components.
248
+ tmpl.format
249
+ end
250
+
251
+
252
+ def _format_Currency(c) # :nodoc:
253
+ x = ''
254
+ x << '<span class="currency_code">' if @html
255
+ x << c.code.to_s
256
+ x << '</span>' if @html
257
+ x
258
+ end
259
+
260
+
261
+ def _format_Time(t) # :nodoc:
262
+ x = ''
263
+ x << t.getutc.xmlschema(@time_fractional_digits) if t
264
+ x
265
+ end
266
+
267
+
268
+ @@empty_hash = { }
269
+ @@empty_hash.freeze
270
+
271
+ # Format a Money object as a String.
272
+ #
273
+ # m = Money.new("1234567.89")
274
+ # m.to_s(:code => true, :symbol => false)
275
+ # => "1,234,567.89 USD"
276
+ #
277
+ def format(m, opt = @@empty_hash)
278
+ fmt = self
279
+
280
+ unless opt.empty?
281
+ fmt = fmt.clone
282
+ opt.each_pair{ | k, v | fmt.send("#{k}=", v) }
283
+ end
284
+
285
+ # $stderr.puts "format(opt = #{opt.inspect})"
286
+ fmt._format(m, opt[:currency]) # Allow override of current currency.
287
+ end
288
+
289
+ end # class
290
+
@@ -0,0 +1,321 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+ class Currency::Money
5
+ @@money_attributes = { }
6
+
7
+ # Called by money macro when a money attribute
8
+ # is created.
9
+ def self.register_money_attribute(attr_opts)
10
+ (@@money_attributes[attr_opts[:class]] ||= { })[attr_opts[:attr_name]] = attr_opts
11
+ end
12
+
13
+ # Returns an array of option hashes for all the money attributes of
14
+ # this class.
15
+ #
16
+ # Superclass attributes are not included.
17
+ def self.money_attributes_for_class(cls)
18
+ (@@money_atttributes[cls] || { }).values
19
+ end
20
+
21
+ # Iterates through all known money attributes in all classes.
22
+ #
23
+ # each_money_attribute { | money_opts |
24
+ # ...
25
+ # }
26
+ def self.each_money_attribute(&blk)
27
+ @@money_attributes.each do | cls, hash |
28
+ hash.each do | attr_name, attr_opts |
29
+ yield attr_opts
30
+ end
31
+ end
32
+ end
33
+
34
+ end # class
35
+
36
+
37
+ module Currency::Macro
38
+ def self.append_features(base) # :nodoc:
39
+ # $stderr.puts " Currency::ActiveRecord#append_features(#{base})"
40
+ super
41
+ base.extend(ClassMethods)
42
+ end
43
+
44
+
45
+
46
+ # == Macro Suppport
47
+ #
48
+ # Support for Money attributes.
49
+ #
50
+ # require 'currency'
51
+ # require 'currency/macro'
52
+ #
53
+ # class SomeClass
54
+ # include ::Currency::Macro
55
+ # attr_accessor :amount
56
+ # attr_money :amount_money, :value => :amount, :currency_fixed => :USD, :rep => :float
57
+ # end
58
+ #
59
+ # x = SomeClass.new
60
+ # x.amount = 123.45
61
+ # x.amount
62
+ # # => 123.45
63
+ # x.amount_money
64
+ # # => $123.45 USD
65
+ # x.amount_money = x.amount_money + "12.45"
66
+ # # => $135.90 USD
67
+ # x.amount
68
+ # # => 135.9
69
+ # x.amount = 45.951
70
+ # x.amount_money
71
+ # # => $45.95 USD
72
+ # x.amount
73
+ # # => 45.951
74
+ #
75
+ module ClassMethods
76
+
77
+ # Defines a Money object attribute that is bound
78
+ # to other attributes.
79
+ #
80
+ # Options:
81
+ #
82
+ # :value => undef
83
+ #
84
+ # Defines the value attribute to use for storing the money value.
85
+ # Defaults to the attribute name.
86
+ #
87
+ # If this attribute is different from the attribute name,
88
+ # the money object will intercept #{value}=(x) to flush
89
+ # any cached Money object.
90
+ #
91
+ # :readonly => false
92
+ #
93
+ # If true, the underlying attribute is readonly. Thus the Money object
94
+ # cannot be cached. This is useful for computed money values.
95
+ #
96
+ # :rep => :float
97
+ #
98
+ # This option specifies how the value attribute stores Money values.
99
+ # if :rep is :rep, then the value is stored as a scaled integer as
100
+ # defined by the Currency.
101
+ # If :rep is :float, or :integer the corresponding #to_f or #to_i
102
+ # method is used.
103
+ # Defaults to :float.
104
+ #
105
+ # :currency => undef
106
+ #
107
+ # Defines the attribute used to store and
108
+ # retrieve the Money's Currency 3-letter ISO code.
109
+ #
110
+ # :currency_fixed => currency_code (e.g.: :USD)
111
+ #
112
+ # Defines the Currency to use for storing a normalized Money
113
+ # value.
114
+ #
115
+ # All Money values will be converted to this Currency before
116
+ # storing. This allows SQL summary operations,
117
+ # like SUM(), MAX(), AVG(), etc., to produce meaningful results,
118
+ # regardless of the initial currency specified. If this
119
+ # option is used, subsequent reads will be in the specified
120
+ # normalization :currency_fixed.
121
+ #
122
+ # :currency_preferred => undef
123
+ #
124
+ # Defines the name of attribute used to store and
125
+ # retrieve the Money's Currency ISO code. This option can be used
126
+ # with normalized Money values to retrieve the Money value
127
+ # in its original Currency, while
128
+ # allowing SQL summary operations on the normalized Money values
129
+ # to still be valid.
130
+ #
131
+ # :currency_update => undef
132
+ #
133
+ # If true, the currency attribute is updated upon setting the
134
+ # money attribute.
135
+ #
136
+ # :time => undef
137
+ #
138
+ # Defines the attribute used to
139
+ # retrieve the Money's time. If this option is used, each
140
+ # Money value will use this attribute during historical Currency
141
+ # conversions.
142
+ #
143
+ # Money values can share a time value with other attributes
144
+ # (e.g. a created_on column in ActiveRecord::Base).
145
+ #
146
+ # If this option is true, the money time attribute will be named
147
+ # "#{attr_name}_time" and :time_update will be true.
148
+ #
149
+ # :time_update => undef
150
+ #
151
+ # If true, the Money time value is updated upon setting the
152
+ # money attribute.
153
+ #
154
+ def attr_money(attr_name, *opts)
155
+ opts = Hash[*opts]
156
+
157
+ attr_name = attr_name.to_s
158
+ opts[:class] = self
159
+ opts[:name] = attr_name.intern
160
+ ::Currency::Money.register_money_attribute(opts)
161
+
162
+ value = opts[:value] || opts[:name]
163
+ opts[:value] = value
164
+ write_value = opts[:write_value] ||= "self.#{value} = "
165
+
166
+ # Intercept value setter?
167
+ if ! opts[:readonly] && value.to_s != attr_name.to_s
168
+ alias_accessor = <<-"end_eval"
169
+ alias :before_money_#{value}= :#{value}=
170
+
171
+ def #{value}=(__value)
172
+ @#{attr_name} = nil # uncache
173
+ self.before_money_#{value} = __value
174
+ end
175
+
176
+ end_eval
177
+ end
178
+
179
+ # How to convert between numeric representation and Money.
180
+ rep = opts[:rep] ||= :float
181
+ to_rep = opts[:to_rep]
182
+ from_rep = opts[:from_rep]
183
+ if rep == :rep
184
+ to_rep = 'rep'
185
+ from_rep = '::Currency::Money.new_rep'
186
+ else
187
+ case rep
188
+ when :float
189
+ to_rep = 'to_f'
190
+ when :integer
191
+ to_rep = 'to_i'
192
+ else
193
+ raise ::Currency::Exception::InvalidMoneyValue, "Cannot use value representation: #{rep.inspect}"
194
+ end
195
+ from_rep = '::Currency::Money.new'
196
+ end
197
+ to_rep = to_rep.to_s
198
+ from_rep = from_rep.to_s
199
+
200
+ # Money time values.
201
+ time = opts[:time]
202
+ write_time = ''
203
+ if time
204
+ if time == true
205
+ time = "#{attr_name}_time"
206
+ opts[:time_update] = true
207
+ end
208
+ read_time = "self.#{time}"
209
+ end
210
+ opts[:time] = time
211
+ if opts[:time_update]
212
+ write_time = "self.#{time} = #{attr_name}_money && #{attr_name}_money.time"
213
+ end
214
+ time ||= 'nil'
215
+ read_time ||= time
216
+
217
+ currency_fixed = opts[:currency_fixed]
218
+ currency_fixed &&= ":#{currency_fixed}"
219
+
220
+ currency = opts[:currency]
221
+ if currency == true
222
+ currency = currency.to_s
223
+ currency = "self.#{attr_name}_currency"
224
+ end
225
+ if currency
226
+ read_currency = "self.#{currency}"
227
+ if opts[:currency_update]
228
+ write_currency = "self.#{currency} = #{attr_name}_money.nil? ? nil : #{attr_name}_money.currency.code"
229
+ else
230
+ convert_currency = "#{attr_name}_money = #{attr_name}_money.convert(#{read_currency}, #{read_time})"
231
+ end
232
+ end
233
+ opts[:currency] = currency
234
+ write_currency ||= ''
235
+ convert_currency ||= ''
236
+
237
+ currency_preferred = opts[:currency_preferred]
238
+ if currency_preferred
239
+ currency_preferred = currency_preferred.to_s
240
+ read_preferred_currency = "@#{attr_name} = @#{attr_name}.convert(#{currency_preferred}, #{read_time})"
241
+ write_preferred_currency = "self.#{currency_preferred} = @#{attr_name}_money.currency.code"
242
+ end
243
+
244
+ currency ||= currency_fixed
245
+ read_currency ||= currency
246
+
247
+ alias_accessor ||= ''
248
+
249
+ validate ||= ''
250
+
251
+ if opts[:readonly]
252
+ eval_opts = [ (opts[:module_eval] = x = <<-"end_eval"), __FILE__, __LINE__ ]
253
+ #{validate}
254
+
255
+ def #{attr_name}
256
+ #{attr_name}_rep = #{value}
257
+ if #{attr_name}_rep != nil
258
+ #{attr_name} = #{from_rep}(#{attr_name}_rep, #{read_currency} || #{currency}, #{read_time} || #{time})
259
+ #{read_preferred_currency}
260
+ else
261
+ #{attr_name} = nil
262
+ end
263
+ #{attr_name}
264
+ end
265
+
266
+ end_eval
267
+ else
268
+ eval_opts = [ (opts[:module_eval] = x = <<-"end_eval"), __FILE__, __LINE__ ]
269
+ #{validate}
270
+
271
+ #{alias_accessor}
272
+
273
+ def #{attr_name}
274
+ unless @#{attr_name}
275
+ #{attr_name}_rep = #{value}
276
+ if #{attr_name}_rep != nil
277
+ @#{attr_name} = #{from_rep}(#{attr_name}_rep, #{read_currency} || #{currency}, #{read_time} || #{time})
278
+ #{read_preferred_currency}
279
+ end
280
+ end
281
+ @#{attr_name}
282
+ end
283
+
284
+
285
+ def #{attr_name}=(value)
286
+ if value == nil
287
+ #{attr_name}_money = nil
288
+ elsif value.kind_of?(Integer) || value.kind_of?(Float) || value.kind_of?(String)
289
+ #{attr_name}_money = ::Currency.Money(value, #{read_currency}, #{read_time})
290
+ #{write_preferred_currency}
291
+ elsif value.kind_of?(::Currency::Money)
292
+ #{attr_name}_money = value
293
+ #{write_preferred_currency}
294
+ #{convert_currency}
295
+ else
296
+ raise ::Currency::Exception::InvalidMoneyValue, value
297
+ end
298
+
299
+ @#{attr_name} = #{attr_name}_money
300
+ #{write_value}(#{attr_name}_money.nil? ? nil : #{attr_name}_money.#{to_rep})
301
+ #{write_currency}
302
+ #{write_time}
303
+
304
+ value
305
+ end
306
+
307
+ end_eval
308
+ end
309
+
310
+ # $stderr.puts " CODE = #{x}"
311
+ module_eval(*eval_opts)
312
+ end
313
+ end # module
314
+ end # module
315
+
316
+
317
+ # Use include ::Currency::Macro
318
+ #::Object.class_eval do
319
+ # include Currency::Macro
320
+ #end
321
+