module Enum::AttrSupport
Provides helper methods to integrate enumerated constants (Enum
) into your model layer. Given an enum defined like so:
module UserType enum :guest, 0 enum :member, 1 enum :admin, 2 end
To add an enumerated value to a Rails model, simply add a column of type :integer to your model, then declare it like so:
class User < ActiveRecord::Base # A symbol will be assumed to map to a valid enum class name enum_attr :user_type # If your attribute's name won't map automatically, you can pass a hash instead enum_attr :another_user_type => UserType # If you have multiple enum attributes, you can add them all in one call, just make # sure mapped attrs are at the end or you'll get an error enum_attr :user_type, :another_user_type => UserType end
When using non-model classes, it's the same syntax:
class User enum_attr :user_type end
This will tell your class/model that the user_type attribute contains values from the UserType enum, and will add:
@user.user_type => integer value or nil @user.user_type_admin? => true if object's user_type value == UserType::ADMIN @user.user_type_admin! => set the object's user_type to be UserType::ADMIN (does not save model!) @user.user_type_as_key => returns the key form of the current field value, eg :member @user.user_type_as_name => returns text name of the current field's value, eg 'Guest'
In addition, you can set enum attributes via key, eg:
@user.user_type = :admin
and the key will be converted to a value on the fly.
ActiveRecord models get a few extras. To start, each enum attribute will add a smart scope:
User.with_user_type(UserType::MEMBER) => scope returning a relation selecting User instances where user_type's value == UserType::MEMBER
In addition, enum attributes will show up in inspect output as e.g. UserType::GUEST instead of 0. Enum
attributes will also generate an automatic inclusion validation to ensure that the attribute never ends up being an invalid value.
Public Instance Methods
Call with enum_attr
:field => Enum
# File lib/iron/enum/attr_support.rb, line 55 def enum_attr(*array_or_map) # Convert to a full map field_to_enum_map = {} array_or_map.each do |info| if info.is_a?(Symbol) name = info.to_s.capitalize.gsub(/\_([a-z])/) { $1.capitalize } klass = Object.const_get(name) rescue nil raise "Unknown enum class '#{name}' for enum_attr :#{info}" unless klass field_to_enum_map[info] = klass elsif info.is_a?(Hash) field_to_enum_map.merge!(info) else raise "Invalid enum_attr key: #{info.inspect}" end end # Save off the attr map @enum_attrs ||= {} @enum_attrs.merge!(field_to_enum_map) # Run each newly added enum attribute field_to_enum_map.each_pair do |attr_field, enum| # Convert Enum to "Enum" enum_klass = enum.to_s # Set up general use sugar - allows calling: # attr_as_key to get back eg :production or :online instead of 1 or 5 # attr_as_name to get back eg "Production" or "Online" class_eval <<-eos, __FILE__, __LINE__ + 1 def #{attr_field}_as_key #{enum_klass}.key(self.#{attr_field}) end def #{attr_field}_as_name #{enum_klass}.name(self.#{attr_field}) end eos # Get all the possible values for this enum in :key format (ie as symbols) enum.keys.each do |key| # Get the value for this key (ie in integer format) val = enum.value(key) # Build sugar for testing and setting the attribute's enumerated value class_eval <<-eos, __FILE__, __LINE__ + 1 def #{attr_field}_#{key}? self.#{attr_field} == #{val} end def #{attr_field}_#{key}! self.#{attr_field} = #{val} end eos end if defined?(ActiveRecord) && self < ActiveRecord::Base # Define a finder scope scope "with_#{attr_field}", lambda {|*vals| vals.flatten! if vals.empty? where("?", false) elsif vals.count == 1 where(attr_field => enum.value(vals.first)) else where(attr_field => enum.values(vals)) end } # Define a validation validates attr_field, :inclusion => { :in => enum.values, :message => "%{value} is not a valid #{enum_klass} value", :allow_nil => true } # Override default setter to allow setting an enum attribute via key class_eval <<-eos, __FILE__, __LINE__ + 1 def #{attr_field}=(val) val = nil if val.is_a?(String) && val.empty? write_attribute(:#{attr_field}, #{enum_klass}.value(val)) end eos else # Create getter/setter to allow setting an enum attribute via key class_eval <<-eos, __FILE__, __LINE__ + 1 def #{attr_field} @#{attr_field} end def #{attr_field}=(val) val = nil if val.is_a?(String) && val.empty? @#{attr_field} = #{enum_klass}.value(val) end eos end end end
True if the given symbol maps to an enum-backed attribute
# File lib/iron/enum/attr_support.rb, line 159 def enum_attr?(name) return false unless @enum_attrs @enum_attrs.key?(name) end
Gets the enum class for a given attribute, or nil for none
# File lib/iron/enum/attr_support.rb, line 165 def enum_for_attr(name) return nil unless @enum_attrs @enum_attrs[name] end