aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/enum.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/enum.rb')
-rw-r--r--activerecord/lib/active_record/enum.rb99
1 files changed, 64 insertions, 35 deletions
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 2b99899e42..8655f68308 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -18,10 +18,9 @@ module ActiveRecord
# conversation.archived? # => true
# conversation.status # => "archived"
#
- # # conversation.update! status: 1
+ # # conversation.status = 1
# conversation.status = "archived"
#
- # # conversation.update! status: nil
# conversation.status = nil
# conversation.status.nil? # => true
# conversation.status # => nil
@@ -47,13 +46,13 @@ module ActiveRecord
# Good practice is to let the first declared status be the default.
#
# Finally, it's also possible to explicitly map the relation between attribute and
- # database integer with a +Hash+:
+ # database integer with a hash:
#
# class Conversation < ActiveRecord::Base
# enum status: { active: 0, archived: 1 }
# end
#
- # Note that when an +Array+ is used, the implicit mapping from the values to database
+ # Note that when an array is used, the implicit mapping from the values to database
# integers is derived from the order the values appear in the array. In the example,
# <tt>:active</tt> is mapped to +0+ as it's the first element, and <tt>:archived</tt>
# is mapped to +1+. In general, the +i+-th element is mapped to <tt>i-1</tt> in the
@@ -61,7 +60,7 @@ module ActiveRecord
#
# Therefore, once a value is added to the enum array, its position in the array must
# be maintained, and new values should only be added to the end of the array. To
- # remove unused values, the explicit +Hash+ syntax should be used.
+ # remove unused values, the explicit hash syntax should be used.
#
# In rare circumstances you might need to access the mapping directly.
# The mappings are exposed through a class method with the pluralized attribute
@@ -75,6 +74,24 @@ module ActiveRecord
#
# Conversation.where("status <> ?", Conversation.statuses[:archived])
#
+ # You can use the +:_prefix+ or +:_suffix+ options when you need to define
+ # multiple enums with same values. If the passed value is +true+, the methods
+ # are prefixed/suffixed with the name of the enum. It is also possible to
+ # supply a custom value:
+ #
+ # class Conversation < ActiveRecord::Base
+ # enum status: [:active, :archived], _suffix: true
+ # enum comments_status: [:active, :inactive], _prefix: :comments
+ # end
+ #
+ # With the above example, the bang and predicate methods along with the
+ # associated scopes are now prefixed and/or suffixed accordingly:
+ #
+ # conversation.active_status!
+ # conversation.archived_status? # => false
+ #
+ # conversation.comments_inactive!
+ # conversation.comments_active? # => false
module Enum
def self.extended(base) # :nodoc:
@@ -87,7 +104,7 @@ module ActiveRecord
super
end
- class EnumType < Type::Value
+ class EnumType < Type::Value # :nodoc:
def initialize(name, mapping)
@name = name
@mapping = mapping
@@ -101,19 +118,25 @@ module ActiveRecord
elsif mapping.has_value?(value)
mapping.key(value)
else
- raise ArgumentError, "'#{value}' is not a valid #{name}"
+ assert_valid_value(value)
end
end
def deserialize(value)
return if value.nil?
- mapping.key(value.to_i)
+ mapping.key(value)
end
def serialize(value)
mapping.fetch(value, value)
end
+ def assert_valid_value(value)
+ unless value.blank? || mapping.has_key?(value) || mapping.has_value?(value)
+ raise ArgumentError, "'#{value}' is not a valid #{name}"
+ end
+ end
+
protected
attr_reader :name, :mapping
@@ -121,6 +144,8 @@ module ActiveRecord
def enum(definitions)
klass = self
+ enum_prefix = definitions.delete(:_prefix)
+ enum_suffix = definitions.delete(:_suffix)
definitions.each do |name, values|
# statuses = { }
enum_values = ActiveSupport::HashWithIndifferentAccess.new
@@ -138,19 +163,31 @@ module ActiveRecord
_enum_methods_module.module_eval do
pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
pairs.each do |value, i|
+ if enum_prefix == true
+ prefix = "#{name}_"
+ elsif enum_prefix
+ prefix = "#{enum_prefix}_"
+ end
+ if enum_suffix == true
+ suffix = "_#{name}"
+ elsif enum_suffix
+ suffix = "_#{enum_suffix}"
+ end
+
+ value_method_name = "#{prefix}#{value}#{suffix}"
enum_values[value] = i
# def active?() status == 0 end
- klass.send(:detect_enum_conflict!, name, "#{value}?")
- define_method("#{value}?") { self[name] == value.to_s }
+ klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
+ define_method("#{value_method_name}?") { self[name] == value.to_s }
# def active!() update! status: :active end
- klass.send(:detect_enum_conflict!, name, "#{value}!")
- define_method("#{value}!") { update! name => value }
+ klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
+ define_method("#{value_method_name}!") { update! name => value }
# scope :active, -> { where status: 0 }
- klass.send(:detect_enum_conflict!, name, value, true)
- klass.scope value, -> { klass.where name => value }
+ klass.send(:detect_enum_conflict!, name, value_method_name, true)
+ klass.scope value_method_name, -> { where(name => value) }
end
end
defined_enums[name.to_s] = enum_values
@@ -173,30 +210,22 @@ module ActiveRecord
def detect_enum_conflict!(enum_name, method_name, klass_method = false)
if klass_method && dangerous_class_method?(method_name)
- raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
- enum: enum_name,
- klass: self.name,
- type: 'class',
- method: method_name,
- source: 'Active Record'
- }
+ raise_conflict_error(enum_name, method_name, type: 'class')
elsif !klass_method && dangerous_attribute_method?(method_name)
- raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
- enum: enum_name,
- klass: self.name,
- type: 'instance',
- method: method_name,
- source: 'Active Record'
- }
+ raise_conflict_error(enum_name, method_name)
elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
- raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
- enum: enum_name,
- klass: self.name,
- type: 'instance',
- method: method_name,
- source: 'another enum'
- }
+ raise_conflict_error(enum_name, method_name, source: 'another enum')
end
end
+
+ def raise_conflict_error(enum_name, method_name, type: 'instance', source: 'Active Record')
+ raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
+ enum: enum_name,
+ klass: self.name,
+ type: type,
+ method: method_name,
+ source: source
+ }
+ end
end
end