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
-
#attr_money(attr_name, *opts) ⇒ Object
Defines a Money object attribute that is bound to a database column.
-
#money(*args) ⇒ Object
Deprecated: use attr_money.
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 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
# 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.to_sym ::ActiveRecord::Base.register_money_attribute(opts) column = opts[:column] || opts[:attr_name] opts[:column] = column # TODO: rewrite with define_method (dvd, 15-03-2009) 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 alias_accessor ||= '' currency = opts[:currency] currency_column = opts[:currency_column] if currency_column && ! currency_column.kind_of?(String) currency_column = "#{column}_currency" end if currency_column read_currency = "read_attribute(:#{currency_column})" 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 # ================================================================================================= # = Define the currency_column setter, so that the Money object changes when the currency changes = # ================================================================================================= if currency_column currency_column_setter = %Q{ def #{currency_column}=(currency_code) @#{attr_name} = nil write_attribute(:#{currency_column}, currency_code) end } class_eval currency_column_setter, __FILE__, __LINE__ end class_eval (opts[:module_eval] = x = <<-"end_eval"), __FILE__, __LINE__ #{validate} #{alias_accessor} # Getter def #{attr_name} unless @#{attr_name} rep = read_attribute(:#{column}) unless rep.nil? @#{attr_name} = ::Currency::Money.new_rep(rep, #{read_currency} || #{currency}, #{read_time} || #{time}) #{read_preferred_currency} end end @#{attr_name} end # Setter def #{attr_name}=(value) if value.nil? || value.to_s.strip == '' #{attr_name}_money = nil elsif value.kind_of?(Integer) || value.kind_of?(String) || value.kind_of?(Float) #{attr_name}_money = ::Currency::Money(value, #{read_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 # TODO: Really needed? Isn't the write_attribute enough? (answer: no, because the getter method does an "if @#{attr_name}" to check if it's set) write_attribute(:#{column}, #{attr_name}_money.nil? ? nil : #{attr_name}_money.rep) #{write_time} value end def #{attr_name}_before_type_cast #{attr_name}.to_f if #{attr_name} end end_eval =begin Replaced the _before_type_cast because it's buggy and weird: Bug: if the Currency::Formatter.default is set to include the currency code (:code => true) then the call below to format() will leave the code in. When the validates_numericality_of kicks in it can't cast to Float (yes, validates_numericality_of basically does just that) because of the "USD" of the currency code and everything fails. All the time. Weird: assigning to "x" doesn't really make any sense, just useless overhead. Using the rare &&= is not a big win over something like: x && x.format(..., ...) and actually longer too. The intention of the _before_type_cast method is to return a raw, unformatted value. When it does work, it returns a string on the form "123.456". Why not cast to Float right away? Arguably, the "raw" currency value is the integer rep stored in the db, but that wouldn't work very well with any known rails validations. I think casting to Float is reasonable. The taste Kurt Stephens has for weird Ruby code never ceases to amaze me. :) (dvd, 05-02-2009) 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 # $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 |