Module: Currency::ActiveRecord::ClassMethods

Defined in:
lib/currency/active_record.rb

Overview

ActiveRecord Suppport

Support for Money attributes in ActiveRecord::Base subclasses:

require 'currency'
require 'currency/active_record'

class Entry < ActiveRecord::Base
   attr_money :amount
end

Instance Method Summary collapse

Instance Method Details

#attr_money(attr_name, *opts) ⇒ Object

Defines a Money object attribute that is bound to a database column. The database column to store the Money value representation is assumed to be INTEGER and will store Money#rep values.

Options:

:column => undef

Defines the column to use for storing the money value. Defaults to the attribute name.

If this column is different from the attribute name, the money object will intercept column=(x) to flush any cached Money object.

:currency => currency_code (e.g.: :USD)

Defines the Currency to use for storing a normalized Money value.

All Money values will be converted to this Currency before storing. This allows SQL summary operations, like SUM(), MAX(), AVG(), etc., to produce meaningful results, regardless of the initial currency specified. If this option is used, subsequent reads will be in the specified normalization :currency.

:currency_column => undef

Defines the name of the CHAR(3) column used to store and retrieve the Money’s Currency code. If this option is used, each record may use a different Currency to store the result, such that SQL summary operations, like SUM(), MAX(), AVG(), may return meaningless results.

:currency_preferred_column => undef

Defines the name of a CHAR(3) column used to store and retrieve the Money’s Currency code. This option can be used with normalized Money values to retrieve the Money value in its original Currency, while allowing SQL summary operations on the normalized Money values to still be valid.

:time => undef

Defines the name of attribute used to retrieve the Money’s time. If this option is used, each Money value will use this attribute during historical Currency conversions.

Money values can share a time value with other attributes (e.g. a created_on column).

If this option is true, the money time attribute will be named “#attr_name_time” and :time_update will be true.

:time_update => undef

If true, the Money time value is updated upon setting the money attribute.



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/currency/active_record.rb', line 136

def attr_money(attr_name, *opts)
  opts = Hash[*opts]

  attr_name = attr_name.to_s
  opts[:class] = self
  opts[:table] = self.table_name
  opts[:attr_name] = attr_name.intern
  ::ActiveRecord::Base.register_money_attribute(opts)

  column = opts[:column] || opts[:attr_name]
  opts[:column] = column

  if column.to_s != attr_name.to_s
    alias_accessor = <<-"end_eval"
alias :before_money_#{column}=, :#{column}=

def #{column}=(__value)
  @{attr_name} = nil # uncache
  before_money#{column} = __value
end

end_eval
  end

  currency = opts[:currency]

  currency_column = opts[:currency_column]
  if currency_column && ! currency_column.kind_of?(String)
    currency_column = currency_column.to_s
    currency_column = "#{column}_currency"
  end
  if currency_column
    read_currency = "read_attribute(:#{currency_column.to_s})"
    write_currency = "write_attribute(:#{currency_column}, #{attr_name}_money.nil? ? nil : #{attr_name}_money.currency.code.to_s)"
  end
  opts[:currency_column] = currency_column

  currency_preferred_column = opts[:currency_preferred_column]
  if currency_preferred_column
    currency_preferred_column = currency_preferred_column.to_s
    read_preferred_currency = "@#{attr_name} = @#{attr_name}.convert(read_attribute(:#{currency_preferred_column}))"
    write_preferred_currency = "write_attribute(:#{currency_preferred_column}, @#{attr_name}_money.currency.code)"
  end

  time = opts[:time]
  write_time = ''
  if time
    if time == true
      time = "#{attr_name}_time"
      opts[:time_update] = true
    end
    read_time = "self.#{time}"
  end
  opts[:time] = time
  if opts[:time_update]
    write_time = "self.#{time} = #{attr_name}_money && #{attr_name}_money.time"
  end

  currency ||= ':USD'
  time ||= 'nil'

  read_currency ||= currency
  read_time ||= time

  money_rep ||= "#{attr_name}_money.rep"

  validate_allow_nil = opts[:allow_nil] ? ', :allow_nil => true' : ''
  validate = "# Validation\n"
  validate << "\nvalidates_numericality_of :#{attr_name}#{validate_allow_nil}\n"
  validate << "\nvalidates_format_of :#{currency_column}, :with => /^[A-Z][A-Z][A-Z]$/#{validate_allow_nil}\n" if currency_column

 
  alias_accessor ||= ''

  module_eval (opts[:module_eval] = x = <<-"end_eval"), __FILE__, __LINE__
#{validate}

#{alias_accessor}

def #{attr_name}
  # $stderr.puts "  \#{self.class.name}##{attr_name}"
  unless @#{attr_name}
    #{attr_name}_rep = read_attribute(:#{column})
    unless #{attr_name}_rep.nil?
@#{attr_name} = ::Currency::Money.new_rep(#{attr_name}_rep, #{read_currency} || #{currency}, #{read_time} || #{time})
#{read_preferred_currency}
    end
  end
  @#{attr_name}
end

def #{attr_name}=(value)
  if value.nil?
    #{attr_name}_money = nil
  elsif value.kind_of?(Integer) || value.kind_of?(String) || value.kind_of?(Float)
    #{attr_name}_money = ::Currency::Money(value, #{currency})
    #{write_preferred_currency}
  elsif value.kind_of?(::Currency::Money)
    #{attr_name}_money = value
    #{write_preferred_currency}
    #{write_currency ? write_currency : "#{attr_name}_money = #{attr_name}_money.convert(#{currency})"}
  else
    raise ::Currency::Exception::InvalidMoneyValue, value
  end

  @#{attr_name} = #{attr_name}_money
  
  write_attribute(:#{column}, #{attr_name}_money.nil? ? nil : #{money_rep})
  #{write_time}

  value
end

def #{attr_name}_before_type_cast
  # FIXME: User cannot specify Currency
  x = #{attr_name}
  x &&= x.format(:symbol => false, :currency => false, :thousands => false)
  x
end

end_eval
  # $stderr.puts "   CODE = #{x}"
end

#money(*args) ⇒ Object

Deprecated: use attr_money.



67
68
69
70
# File 'lib/currency/active_record.rb', line 67

def money(*args)
  $stderr.puts "WARNING: money(#{args.inspect}) deprecated, use attr_money: in #{caller(1)[0]}"
  attr_money(*args)
end