Module: Enum::AttrSupport
- Defined in:
- lib/iron/enum/attr_support.rb
Overview
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.
Instance Method Summary collapse
-
#enum_attr(*array_or_map) ⇒ Object
Call with enum_attr :field => Enum.
-
#enum_attr?(name) ⇒ Boolean
True if the given symbol maps to an enum-backed attribute.
-
#enum_for_attr(name) ⇒ Object
Gets the enum class for a given attribute, or nil for none.
Instance Method Details
#enum_attr(*array_or_map) ⇒ Object
Call with enum_attr :field => Enum
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# 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 |
#enum_attr?(name) ⇒ Boolean
True if the given symbol maps to an enum-backed attribute
159 160 161 162 |
# File 'lib/iron/enum/attr_support.rb', line 159 def enum_attr?(name) return false unless @enum_attrs @enum_attrs.key?(name) end |
#enum_for_attr(name) ⇒ Object
Gets the enum class for a given attribute, or nil for none
165 166 167 168 |
# File 'lib/iron/enum/attr_support.rb', line 165 def enum_for_attr(name) return nil unless @enum_attrs @enum_attrs[name] end |