module MoneyRails::ActiveRecord::Monetizable::ClassMethods

Attributes

currency[R]
monetized_attributes[R]

Public Instance Methods

monetize(field, *args) click to toggle source
# File lib/money-rails/active_record/monetizable.rb, line 11
def monetize(field, *args)
  options = args.extract_options!

  # Stringify model field name
  subunit_name = field.to_s

  if options[:field_currency] || options[:target_name] ||
    options[:model_currency]
    ActiveSupport::Deprecation.warn("You are using the old " \
      "argument keys of monetize command! Instead use :as, " \
      ":with_currency or :with_model_currency")
  end

  # Optional accessor to be run on an instance to detect currency
  instance_currency_name = options[:with_model_currency] ||
    options[:model_currency] || "currency"
  instance_currency_name = instance_currency_name.to_s

  # This attribute allows per column currency values
  # Overrides row and default currency
  field_currency_name = options[:with_currency] ||
    options[:field_currency] || nil

  name = options[:as] || options[:target_name] || nil

  # Form target name for the money backed ActiveModel field:
  # if a target name is provided then use it
  # if there is a "_cents" suffix then just remove it to create the target name
  # if none of the previous is the case then use a default suffix
  if name
    name = name.to_s
  elsif subunit_name =~ /_cents$/
    name = subunit_name.sub(/_cents$/, "")
  else
    # FIXME: provide a better default
    name = [subunit_name, "money"].join("_")
  end

  # Create a reverse mapping of the monetized attributes
  @monetized_attributes ||= {}
  @monetized_attributes[name.to_sym] = subunit_name
  class << self
    attr_reader :monetized_attributes
  end unless respond_to? :monetized_attributes

  # Include numericality validation if needed
  if MoneyRails.include_validations
    validates_numericality_of subunit_name, :allow_nil => options[:allow_nil]

    # Allow only Money objects or Numeric values!
    validates name.to_sym, 'money_rails/active_model/money' => { :allow_nil => options[:allow_nil] }
  end

  define_method name do |*args|
    amount = send(subunit_name, *args)
    attr_currency = send("currency_for_#{name}")

    # Dont create a new Money instance if the values haven't changed
    memoized = instance_variable_get("@#{name}")
    return memoized if memoized && memoized.cents == amount &&
      memoized.currency == attr_currency

    amount = Money.new(amount, attr_currency) unless amount.blank?

    instance_variable_set "@#{name}", amount
  end

  define_method "#{name}=" do |value|

    # Lets keep the before_type_cast value
    instance_variable_set "@#{name}_money_before_type_cast", value

    if options[:allow_nil] && value.blank?
      money = nil
    else
      begin
        money = value.is_a?(Money) ? value : value.to_money(send("currency_for_#{name}"))
      rescue NoMethodError
        return nil
      end
    end

    send("#{subunit_name}=", money.try(:cents))
    send("#{instance_currency_name}=", money.try(:currency).try(:iso_code)) if self.respond_to?("#{instance_currency_name}=")

    instance_variable_set "@#{name}", money
  end

  define_method "currency_for_#{name}" do
    if self.respond_to?(instance_currency_name) && send(instance_currency_name).present? 
      Money::Currency.find(send(instance_currency_name))
    elsif field_currency_name
      Money::Currency.find(field_currency_name)
    elsif self.class.respond_to?(:currency)
      self.class.currency
    else
      Money.default_currency
    end
  end

  define_method "#{name}_money_before_type_cast" do
    instance_variable_get "@#{name}_money_before_type_cast"
  end

  # Hook to ensure the reset of before_type_cast attr
  # TODO: think of a better way to avoid this
  after_validation do
    instance_variable_set "@#{name}_money_before_type_cast", nil
  end
end
register_currency(currency_name) click to toggle source
# File lib/money-rails/active_record/monetizable.rb, line 122
def register_currency(currency_name)
  # Lookup the given currency_name and raise exception if
  # no currency is found
  currency_object = Money::Currency.find currency_name
  raise(ArgumentError, "Can't find #{currency_name} currency code") unless currency_object

  class_eval do
    @currency = currency_object
    class << self
      attr_reader :currency
    end
  end
end