module ActiveRecord::Enum
def self.extended(base) # :nodoc:
def self.extended(base) # :nodoc: base.class_attribute(:defined_enums, instance_writer: false, default: {}) end
def _enum(name, values, prefix: nil, suffix: nil, scopes: true, instance_methods: true, validate: false, **options)
def _enum(name, values, prefix: nil, suffix: nil, scopes: true, instance_methods: true, validate: false, **options) assert_valid_enum_definition_values(values) assert_valid_enum_options(options) # statuses = { } enum_values = ActiveSupport::HashWithIndifferentAccess.new name = name.to_s # def self.statuses() statuses end detect_enum_conflict!(name, name.pluralize, true) singleton_class.define_method(name.pluralize) { enum_values } defined_enums[name] = enum_values detect_enum_conflict!(name, name) detect_enum_conflict!(name, "#{name}=") attribute(name, **options) decorate_attributes([name]) do |_name, subtype| if subtype == ActiveModel::Type.default_value raise "Undeclared attribute type for enum '#{name}' in #{self.name}. Enums must be" \ " backed by a database column or declared with an explicit type" \ " via `attribute`." end subtype = subtype.subtype if EnumType === subtype EnumType.new(name, enum_values, subtype, raise_on_invalid_values: !validate) end value_method_names = [] _enum_methods_module.module_eval do prefix = if prefix prefix == true ? "#{name}_" : "#{prefix}_" end suffix = if suffix suffix == true ? "_#{name}" : "_#{suffix}" end pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index pairs.each do |label, value| enum_values[label] = value label = label.to_s value_method_name = "#{prefix}#{label}#{suffix}" value_method_names << value_method_name define_enum_methods(name, value_method_name, value, scopes, instance_methods) method_friendly_label = label.gsub(/[\W&&[:ascii:]]+/, "_") value_method_alias = "#{prefix}#{method_friendly_label}#{suffix}" if value_method_alias != value_method_name && !value_method_names.include?(value_method_alias) value_method_names << value_method_alias define_enum_methods(name, value_method_alias, value, scopes, instance_methods) end end end detect_negative_enum_conditions!(value_method_names) if scopes if validate validate = {} unless Hash === validate validates_inclusion_of name, in: enum_values.keys, **validate end enum_values.freeze end
def _enum_methods_module
def _enum_methods_module @_enum_methods_module ||= begin mod = EnumMethods.new(self) include mod mod end end
def assert_valid_enum_definition_values(values)
def assert_valid_enum_definition_values(values) case values when Hash if values.empty? raise ArgumentError, "Enum values #{values} must not be empty." end if values.keys.any?(&:blank?) raise ArgumentError, "Enum values #{values} must not contain a blank name." end when Array if values.empty? raise ArgumentError, "Enum values #{values} must not be empty." end unless values.all?(Symbol) || values.all?(String) raise ArgumentError, "Enum values #{values} must only contain symbols or strings." end if values.any?(&:blank?) raise ArgumentError, "Enum values #{values} must not contain a blank name." end else raise ArgumentError, "Enum values #{values} must be either a non-empty hash or an array." end end
def assert_valid_enum_options(options)
def assert_valid_enum_options(options) invalid_keys = options.keys & %i[_prefix _suffix _scopes _default _instance_methods] unless invalid_keys.empty? raise ArgumentError, "invalid option(s): #{invalid_keys.map(&:inspect).join(", ")}. Valid options are: :prefix, :suffix, :scopes, :default, :instance_methods, and :validate." end end
def detect_enum_conflict!(enum_name, method_name, klass_method = false)
def detect_enum_conflict!(enum_name, method_name, klass_method = false) if klass_method && dangerous_class_method?(method_name) raise_conflict_error(enum_name, method_name, type: "class") elsif klass_method && method_defined_within?(method_name, Relation) raise_conflict_error(enum_name, method_name, type: "class", source: Relation.name) elsif klass_method && method_name.to_sym == :id raise_conflict_error(enum_name, method_name) elsif !klass_method && dangerous_attribute_method?(method_name) raise_conflict_error(enum_name, method_name) elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module) raise_conflict_error(enum_name, method_name, source: "another enum") end end
def detect_negative_enum_conditions!(method_names)
def detect_negative_enum_conditions!(method_names) return unless logger method_names.select { |m| m.start_with?("not_") }.each do |potential_not| inverted_form = potential_not.sub("not_", "") if method_names.include?(inverted_form) logger.warn "Enum element '#{potential_not}' in #{self.name} uses the prefix 'not_'." \ " This has caused a conflict with auto generated negative scopes." \ " Avoid using enum elements starting with 'not' where the positive form is also an element." end end end
def enum(name, values = nil, **options)
def enum(name, values = nil, **options) values, options = options, {} unless values _enum(name, values, **options) end
def inherited(base)
def inherited(base) base.defined_enums = defined_enums.deep_dup super end
def raise_conflict_error(enum_name, method_name, type: "instance", source: "Active Record")
def raise_conflict_error(enum_name, method_name, type: "instance", source: "Active Record") raise ArgumentError, ENUM_CONFLICT_MESSAGE % { enum: enum_name, klass: name, type: type, method: method_name, source: source } end