module Enumify::Model::ClassMethods

Public Instance Methods

enumify(parameter, vals=[], opts={}) click to toggle source
# File lib/enumify/model.rb, line 9
def enumify(parameter, vals=[], opts={})

  validates_inclusion_of parameter, :in => vals, :allow_nil => !!opts[:allow_nil]
  paramater_string = parameter.to_s

  prefix = ''
  if opts[:prefix] == true
    prefix = "#{paramater_string}_"
  elsif opts[:prefix].present?
    prefix = "#{opts[:prefix].to_s}_"
  end

  constant = opts.fetch(:constant, true)
  if constant
    const_name = constant === true ? paramater_string.pluralize : constant.to_s

    const_set(const_name.upcase, vals)
  end

  define_method "#{paramater_string}" do
    attr = read_attribute(parameter)
    (attr.nil? || attr.empty?) ? nil : attr.to_sym
  end

  define_method "#{paramater_string}=" do |value|
    send("_set_#{paramater_string}", value, false)
  end

  self.class_eval do

    private

    define_method "_set_#{paramater_string}" do |value, should_save|

      value = value and value.to_sym
      old = read_attribute(parameter) ? read_attribute(parameter).to_sym : nil
      return value if old == value
      write_attribute(parameter, (value and value.to_s))
      save if should_save
      send("#{paramater_string}_changed", old, value) if respond_to?("#{paramater_string}_changed", true) and !old.nil?
      return value
    end

  end

  vals.each do |val|
    attribute = prefix + val.to_s
    query_method = "#{attribute}?"
    bang_method = "#{attribute}!"

    raise "Collision in enum values method #{attribute}" if respond_to?(query_method) or respond_to?(bang_method) or respond_to?(attribute)

    define_method query_method do
      send("#{paramater_string}") == val
    end

    define_method bang_method do
      send("_set_#{paramater_string}", val, true)
    end

    scope attribute.to_sym, lambda { where(parameter.to_sym => val.to_s) }
  end

  # We want to first define all the "positive" scopes and only then define
  # the "negation scopes", to make sure they don't override previous scopes
  vals.each do |val|
    # We need to prefix the field with the table name since if this scope will
    # be used in a joined query with other models that have the same enum field then
    # it will fail on ambiguous column name.
    negative_scope = "not_" + prefix + val.to_s
    unless respond_to?(negative_scope)
      scope negative_scope, lambda { where("#{self.table_name}.#{parameter} != ?", val.to_s) }
    end
  end
end