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, **options)
def _enum(name, values, prefix: nil, suffix: nil, scopes: true, **options) assert_valid_enum_definition_values(values) # 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) do |subtype| subtype = subtype.subtype if EnumType === subtype EnumType.new(name, enum_values, subtype) 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) 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) end end end detect_negative_enum_conditions!(value_method_names) if scopes 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) unless values.is_a?(Hash) || values.all?(Symbol) || values.all?(String) error_message = <<~MSG Enum values #{values} must be either a hash, an array of symbols, or an array of strings. MSG raise ArgumentError, error_message end if values.is_a?(Hash) && values.keys.any?(&:blank?) || values.is_a?(Array) && values.any?(&:blank?) raise ArgumentError, "Enum label name must not be blank." 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 && 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 = nil, values = nil, **options)
def enum(name = nil, values = nil, **options) if name values, options = options, {} unless values return _enum(name, values, **options) end definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default) options.transform_keys! { |key| :"#{key[1..-1]}" } definitions.each { |name, values| _enum(name, values, **options) } end
def inherited(base) # :nodoc:
def inherited(base) # :nodoc: 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