Repository: rails/rails
File: activerecord/lib/active_record/enum.rb (Ruby)

Code before:
  #     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:
      base.class_attribute(:defined_enums, instance_writer: false, default: {})
    end

    def inherited(base) # :nodoc:
      base.defined_enums = defined_enums.deep_dup
      super
    end

    class EnumType < Type::Value # :nodoc:
      delegate :type, to: :subtype

      def initialize(name, mapping, subtype)
        @name = name
        @mapping = mapping
        @subtype = subtype
      end

      def cast(value)
        if mapping.has_key?(value)
          value.to_s
        elsif mapping.has_value?(value)
          mapping.key(value)
        else
          value.presence
        end
      end

      def deserialize(value)
        mapping.key(subtype.deserialize(value))
      end

      def serialize(value)
        subtype.serialize(mapping.fetch(value, value))
      end

      def serializable?(value, &block)
        subtype.serializable?(mapping.fetch(value, value), &block)

Review comment (performance):
```suggestion
        unless columns_hash.key?(name)
          cast_type = cast_type[type_for_attribute(name)] if Proc === cast_type
          raise UnknownAttributeError.new(self, name) if Enum::EnumType === cast_type
        end
```

A bit of a nitpick, but since we expect this not to happen, it may make sense to optimize for the columns to exist.

Code after:
  #     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:
      base.class_attribute(:defined_enums, instance_writer: false, default: {})
    end

    def inherited(base) # :nodoc:
      base.defined_enums = defined_enums.deep_dup
      super
    end

    def load_schema! # :nodoc:
      attributes_to_define_after_schema_loads.each do |name, (cast_type, _default)|
        unless columns_hash.key?(name)
          cast_type = cast_type[type_for_attribute(name)] if Proc === cast_type
          raise "Unknown enum attribute '#{name}' for #{self.name}" if Enum::EnumType === cast_type
        end
      end
    end

    class EnumType < Type::Value # :nodoc:
      delegate :type, to: :subtype

      def initialize(name, mapping, subtype)
        @name = name
        @mapping = mapping
        @subtype = subtype
      end

      def cast(value)
        if mapping.has_key?(value)
          value.to_s
        elsif mapping.has_value?(value)
          mapping.key(value)
        else
          value.presence
        end
      end

      def deserialize(value)