currency 0.3.3 → 0.4.0
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.
- data/COPYING.txt +339 -0
- data/LICENSE.txt +62 -0
- data/Manifest.txt +37 -14
- data/README.txt +8 -0
- data/Rakefile +42 -8
- data/Releases.txt +26 -0
- data/TODO.txt +1 -0
- data/examples/ex1.rb +3 -3
- data/examples/xe1.rb +3 -2
- data/lib/currency.rb +71 -9
- data/lib/currency/active_record.rb +138 -21
- data/lib/currency/core_extensions.rb +7 -5
- data/lib/currency/currency.rb +94 -177
- data/lib/currency/{currency_factory.rb → currency/factory.rb} +46 -25
- data/lib/currency/currency_version.rb +3 -3
- data/lib/currency/exception.rb +14 -14
- data/lib/currency/exchange.rb +14 -12
- data/lib/currency/exchange/rate.rb +159 -28
- data/lib/currency/exchange/rate/deriver.rb +146 -0
- data/lib/currency/exchange/rate/source.rb +84 -0
- data/lib/currency/exchange/rate/source/base.rb +156 -0
- data/lib/currency/exchange/rate/source/failover.rb +57 -0
- data/lib/currency/exchange/rate/source/historical.rb +79 -0
- data/lib/currency/exchange/rate/source/historical/rate.rb +181 -0
- data/lib/currency/exchange/rate/source/historical/writer.rb +203 -0
- data/lib/currency/exchange/rate/source/new_york_fed.rb +91 -0
- data/lib/currency/exchange/rate/source/provider.rb +105 -0
- data/lib/currency/exchange/rate/source/test.rb +50 -0
- data/lib/currency/exchange/rate/source/the_financials.rb +190 -0
- data/lib/currency/exchange/rate/source/timed_cache.rb +144 -0
- data/lib/currency/exchange/rate/source/xe.rb +166 -0
- data/lib/currency/exchange/time_quantitizer.rb +111 -0
- data/lib/currency/formatter.rb +159 -0
- data/lib/currency/macro.rb +321 -0
- data/lib/currency/money.rb +90 -64
- data/lib/currency/money_helper.rb +6 -5
- data/lib/currency/parser.rb +153 -0
- data/test/ar_column_test.rb +6 -3
- data/test/ar_simple_test.rb +5 -2
- data/test/ar_test_base.rb +39 -33
- data/test/ar_test_core.rb +64 -0
- data/test/formatter_test.rb +81 -0
- data/test/historical_writer_test.rb +184 -0
- data/test/macro_test.rb +109 -0
- data/test/money_test.rb +72 -4
- data/test/new_york_fed_test.rb +57 -0
- data/test/parser_test.rb +60 -0
- data/test/test_base.rb +13 -3
- data/test/time_quantitizer_test.rb +136 -0
- data/test/xe_test.rb +29 -5
- metadata +41 -18
- data/lib/currency/exchange/base.rb +0 -84
- data/lib/currency/exchange/test.rb +0 -39
- data/lib/currency/exchange/xe.rb +0 -250
@@ -0,0 +1,111 @@
|
|
1
|
+
# Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
|
2
|
+
# See LICENSE.txt for details.
|
3
|
+
|
4
|
+
# = Currency::Exchange::TimeQuantitizer
|
5
|
+
#
|
6
|
+
# The Currency::Exchange::TimeQuantitizer quantitizes time values
|
7
|
+
# such that money values and rates at a given time
|
8
|
+
# can be turned into a hash key, depending
|
9
|
+
# on the rate source's temporal accuracy.
|
10
|
+
#
|
11
|
+
class Currency::Exchange::TimeQuantitizer
|
12
|
+
|
13
|
+
def self.current; @current ||= self.new; end
|
14
|
+
def self.current=(x); @current = x; end
|
15
|
+
|
16
|
+
# Time quantitization size.
|
17
|
+
# Defaults to 1 day.
|
18
|
+
attr_accessor :time_quant_size
|
19
|
+
|
20
|
+
# Time quantization offset in seconds.
|
21
|
+
# This is applied to epoch time before quantization.
|
22
|
+
# If nil, uses Time#utc_offset.
|
23
|
+
# Defaults to nil.
|
24
|
+
attr_accessor :time_quant_offset
|
25
|
+
|
26
|
+
def initialize(*opt)
|
27
|
+
@time_quant_size ||= 60 * 60 * 24
|
28
|
+
@time_quant_offset ||= nil
|
29
|
+
opt = Hash[*opt]
|
30
|
+
opt.each_pair{|k,v| self.send("#{k}=", v)}
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
# Normalizes time to a quantitized value.
|
35
|
+
# For example: a time_quant_size of 60 * 60 * 24 will
|
36
|
+
# truncate a rate time to a particular day.
|
37
|
+
#
|
38
|
+
# Subclasses can override this method.
|
39
|
+
def quantitize_time(time)
|
40
|
+
# If nil, then nil.
|
41
|
+
return time unless time
|
42
|
+
|
43
|
+
# Get bucket parameters.
|
44
|
+
was_utc = time.utc?
|
45
|
+
quant_offset = time_quant_offset
|
46
|
+
quant_offset ||= time.utc_offset
|
47
|
+
# $stderr.puts "quant_offset = #{quant_offset}"
|
48
|
+
quant_size = time_quant_size.to_i
|
49
|
+
|
50
|
+
# Get offset from epoch.
|
51
|
+
time = time.tv_sec
|
52
|
+
|
53
|
+
# Remove offset (timezone)
|
54
|
+
time += quant_offset
|
55
|
+
|
56
|
+
# Truncate to quantitize size.
|
57
|
+
time = (time.to_i / quant_size) * quant_size
|
58
|
+
|
59
|
+
# Add offset (timezone)
|
60
|
+
time -= quant_offset
|
61
|
+
|
62
|
+
# Convert back to Time object.
|
63
|
+
time = Time.at(time)
|
64
|
+
|
65
|
+
# Quant to day?
|
66
|
+
# NOTE: is this due to a Ruby bug, or
|
67
|
+
# some wierd UTC time-flow issue, like leap-seconds.
|
68
|
+
if quant_size == 60 * 60 * 24
|
69
|
+
time = time + 60 * 60
|
70
|
+
if was_utc
|
71
|
+
time = time.getutc
|
72
|
+
time = Time.utc(time.year, time.month, time.day, 0, 0, 0, 0)
|
73
|
+
else
|
74
|
+
time = Time.local(time.year, time.month, time.day, 0, 0, 0, 0)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Convert back to UTC?
|
79
|
+
time = time.getutc if was_utc
|
80
|
+
|
81
|
+
time
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns a Range of Time such that:
|
85
|
+
#
|
86
|
+
# range.include?(time)
|
87
|
+
# ! range.include?(time + time_quant_size)
|
88
|
+
# ! range.include?(time - time_quant_size)
|
89
|
+
# range.exclude_end?
|
90
|
+
#
|
91
|
+
# The range.max is end-exclusive to avoid precision issues:
|
92
|
+
#
|
93
|
+
# t = Time.now
|
94
|
+
# => Thu Feb 15 15:32:34 EST 2007
|
95
|
+
# x.quantitize_time_range(t)
|
96
|
+
# => Thu Feb 15 00:00:00 EST 2007...Fri Feb 16 00:00:00 EST 2007
|
97
|
+
#
|
98
|
+
def quantitize_time_range(time)
|
99
|
+
time_0 = quantitize_time(time)
|
100
|
+
time_1 = time_0 + time_quant_size.to_i
|
101
|
+
time_0 ... time_1
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns a simple string rep.
|
105
|
+
def to_s
|
106
|
+
"#<#{self.class.name} #{quant_offset} #{quant_size}>"
|
107
|
+
end
|
108
|
+
|
109
|
+
end # class
|
110
|
+
|
111
|
+
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
|
2
|
+
# See LICENSE.txt for details.
|
3
|
+
|
4
|
+
|
5
|
+
# This class formats a Money value as a String.
|
6
|
+
# Each Currency has a default Formatter.
|
7
|
+
class Currency::Formatter
|
8
|
+
# Defaults to ','
|
9
|
+
attr_accessor :thousands_separator
|
10
|
+
|
11
|
+
# Defaults to '.'
|
12
|
+
attr_accessor :decimal_separator
|
13
|
+
|
14
|
+
# If true, insert _thousands_separator_ between each 3 digits in the whole value.
|
15
|
+
attr_accessor :thousands
|
16
|
+
|
17
|
+
# If true, append _decimal_separator_ and decimal digits after whole value.
|
18
|
+
attr_accessor :cents
|
19
|
+
|
20
|
+
# If true, prefix value with currency symbol.
|
21
|
+
attr_accessor :symbol
|
22
|
+
|
23
|
+
# If true, append currency code.
|
24
|
+
attr_accessor :code
|
25
|
+
|
26
|
+
# If true, use html formatting.
|
27
|
+
#
|
28
|
+
# Currency::Money(12.45, :EUR).to_s(:html => true; :code => true)
|
29
|
+
# => "€12.45 <span class=\"currency_code\">EUR</span>"
|
30
|
+
|
31
|
+
attr_accessor :html
|
32
|
+
|
33
|
+
|
34
|
+
# If passed true, formats for an input field (i.e.: as a number).
|
35
|
+
def as_input_value=(x)
|
36
|
+
if x
|
37
|
+
self.thousands_separator = ''
|
38
|
+
self.decimal_separator = '.'
|
39
|
+
self.thousands = false
|
40
|
+
self.cents = true
|
41
|
+
self.symbol = false
|
42
|
+
self.code = false
|
43
|
+
self.html = false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
@@default = nil
|
49
|
+
# Get the default Formatter.
|
50
|
+
def self.default
|
51
|
+
@@default || self.new
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
# Set the default Formatter.
|
56
|
+
def self.default=(x)
|
57
|
+
@@default = x
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def initialize(opt = { })
|
62
|
+
@thousands_separator = ','
|
63
|
+
@decimal_separator = '.'
|
64
|
+
@thousands = true
|
65
|
+
@cents = true
|
66
|
+
@symbol = true
|
67
|
+
@code = false
|
68
|
+
@html = false
|
69
|
+
|
70
|
+
opt.each_pair{ | k, v | self.send("#{k}=", v) }
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def currency=(x) # :nodoc:
|
75
|
+
# DO NOTHING!
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def _format(m, currency = nil) # :nodoc:
|
80
|
+
# Get currency.
|
81
|
+
currency ||= m.currency
|
82
|
+
|
83
|
+
# Get scaled integer representation for this Currency.
|
84
|
+
# $stderr.puts "m.currency = #{m.currency}, currency => #{currency}"
|
85
|
+
x = m.Money_rep(currency)
|
86
|
+
|
87
|
+
# Remove sign.
|
88
|
+
x = - x if ( neg = x < 0 )
|
89
|
+
|
90
|
+
# Convert to String.
|
91
|
+
x = x.to_s
|
92
|
+
|
93
|
+
# Keep prefixing "0" until filled to scale.
|
94
|
+
while ( x.length <= currency.scale_exp )
|
95
|
+
x = "0" + x
|
96
|
+
end
|
97
|
+
|
98
|
+
# Insert decimal place.
|
99
|
+
whole = x[0 .. currency.format_left]
|
100
|
+
decimal = x[currency.format_right .. -1]
|
101
|
+
|
102
|
+
# Do commas
|
103
|
+
x = whole
|
104
|
+
if @thousands && (@thousands_separator && ! @thousands_separator.empty?)
|
105
|
+
x.reverse!
|
106
|
+
x.gsub!(/(\d\d\d)/) {|y| y + @thousands_separator}
|
107
|
+
x.sub!(/#{@thousands_separator}$/,'')
|
108
|
+
x.reverse!
|
109
|
+
end
|
110
|
+
|
111
|
+
x << @decimal_separator + decimal if @cents && @decimal_separator
|
112
|
+
|
113
|
+
# Put sign back.
|
114
|
+
x = '-' + x if neg
|
115
|
+
|
116
|
+
# Add symbol?
|
117
|
+
x = ((@html && currency.symbol_html) || currency.symbol || '') + x if @symbol
|
118
|
+
|
119
|
+
# Suffix with currency code.
|
120
|
+
if @code
|
121
|
+
x << (' ' + _format_Currency(currency))
|
122
|
+
end
|
123
|
+
|
124
|
+
x
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
def _format_Currency(c) # :nodoc:
|
129
|
+
x = ''
|
130
|
+
x << '<span class="currency_code">' if @html
|
131
|
+
x << c.code.to_s
|
132
|
+
x << '</span>' if @html
|
133
|
+
x
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
@@empty_hash = { }
|
138
|
+
@@empty_hash.freeze
|
139
|
+
|
140
|
+
# Format a Money object as a String.
|
141
|
+
#
|
142
|
+
# m = Money.new("1234567.89")
|
143
|
+
# m.to_s(:code => true, :symbol => false)
|
144
|
+
# => "1,234,567.89 USD"
|
145
|
+
#
|
146
|
+
def format(m, opt = @@empty_hash)
|
147
|
+
fmt = self
|
148
|
+
|
149
|
+
unless opt.empty?
|
150
|
+
fmt = fmt.clone
|
151
|
+
opt.each_pair{ | k, v | fmt.send("#{k}=", v) }
|
152
|
+
end
|
153
|
+
|
154
|
+
# $stderr.puts "format(opt = #{opt.inspect})"
|
155
|
+
fmt._format(m, opt[:currency]) # Allow override of current currency.
|
156
|
+
end
|
157
|
+
|
158
|
+
end # class
|
159
|
+
|
@@ -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
|
+
throw ::Currency::Exception::InvalidMoneyValue.new("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
|
+
throw ::Currency::Exception::InvalidMoneyValue.new(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
|
+
|