Skip to content

Repository files navigation

EnumFields

Enhanced enum-like fields for ActiveRecord models with metadata support

Requirements

  • Ruby >= 3.1
  • Rails >= 6.0 (ActiveRecord and ActiveSupport)

Installation

Add this line to your application's Gemfile:

gem"enum_fields"

And then execute:

bundle install

Configuration

Global Configuration

Configure default behavior for all enum_field declarations:

# config/initializers/enum_fields.rbEnumFields.configuredo |config|
config.scopeable=true# default: trueconfig.validatable=true# default: trueconfig.nullable=true# default: trueconfig.inquirable=true# default: trueend
OptionDefaultDescription
scopeabletrueGenerate query scopes for each enum value
validatabletrueAdd inclusion validation for enum values
nullabletrueAllow nil values in validation (polymorphic columns derive this from the association's optional flag instead)
inquirabletrueGenerate ? inquiry methods for each enum value

Individual enum_field options override global configuration:

EnumFields.configuredo |config|
config.scopeable=falseendclassCampaign < ApplicationRecord# Uses global scopeable: falseenum_field:stage,definitions# Overrides global — scopes are generated for this fieldenum_field:priority,definitions,scopeable: trueend

Usage

Basic Setup

Include the EnumFields module in your ApplicationRecord:

classApplicationRecord < ActiveRecord::BaseincludeEnumFieldsself.abstract_class=trueend

Now all models inheriting from ApplicationRecord can use enum_field.

Defining Enum Fields

Hash Definition (Recommended)

classCampaign < ApplicationRecordenum_field:stage,{pending: {value: "pending",label: "Pending",icon: "clock",color: "yellow",tooltip: "Campaign is awaiting processing",},processing: {value: "processing",label: "Processing",icon: "cog",color: "blue",tooltip: "Campaign is being processed",},shipped: {value: "shipped",label: "Shipped",icon: "truck",color: "green",tooltip: "Campaign has been shipped",},delivered: {value: "delivered",label: "Delivered",icon: "check",color: "green",tooltip: "Campaign has been delivered",},}end

Array Definition (Simple)

classTask < ApplicationRecordenum_field:priority,["low","medium","high"]end

This automatically generates:

{low: {value: "low",label: "low",},medium: {value: "medium",label: "medium",},high: {value: "high",label: "high",},}

Generated Methods

For an enum field defined as:

classCampaign < ApplicationRecordenum_field:stage,{draft: {value: "draft",label: "Draft",icon: "file",color: "gray",},scheduled: {value: "scheduled",label: "Scheduled",icon: "calendar",color: "blue",},completed: {value: "completed",label: "Completed",icon: "check",color: "green",},}end

Class Methods

# Returns the definitions as an HashWithIndifferentAccessCampaign.stages# Returns the count of definitionsCampaign.stages_count# 3# Returns the values of the definitionsCampaign.stage_values# ["draft", "scheduled", "completed"]# Returns the options for form helpersCampaign.stage_options# [["Draft", "draft"], ["Scheduled", "scheduled"], ["Completed", "completed"]]# Returns the value for a specific keyCampaign.draft_stage_value# "draft"Campaign.scheduled_stage_value# "scheduled"Campaign.completed_stage_value# "completed"

Instance Getter/Setter

If the accessor name differs from the column name, getter and setter methods are defined for the accessor.

campaign.stage# "draft"campaign.stage="scheduled"campaign.stage# "scheduled"
  • campaign.stage - Get the current stage value
  • campaign.stage = "scheduled" - Set the stage value

Metadata Methods

The gem automatically creates accessor methods for all properties defined in your enum definitions.

  • value (required) - The actual value stored in the database
  • label (auto-generated if not provided) - A human-readable label

Any additional properties you define (like icon, color, tooltip, etc.) will also get dedicated accessor methods automatically.

# Returns the full metadata hash for current valuecampaign.stage_metadata# => { value: "draft", label: "Draft", icon: "file", color: "gray" }# Access individual propertiescampaign.stage_value# "draft"campaign.stage_label# "Draft"campaign.stage_icon# "file"campaign.stage_color# "gray"

Inquiry Methods

# Returns true if the current value is "draft"campaign.draft_stage?# Returns true if the current value is "scheduled"campaign.scheduled_stage?# Returns true if the current value is "completed"campaign.completed_stage?

Scopes

# Returns all campaigns with draft stageCampaign.draft_stage# Returns all campaigns with scheduled stageCampaign.scheduled_stage# Returns all campaigns with completed stageCampaign.completed_stage

Validation

Automatically validates that the column value is included in the defined values. By default, nil values are allowed (see nullable option).

Options

column

Map the accessor to a different database column name:

enum_field:role,definitions,column: :user_role

scopeable

Controls whether query scopes are generated. Defaults to true. Set to false to skip scope generation:

enum_field:speed,definitions,scopeable: false

validatable

Controls whether inclusion validation is added. Defaults to true. Set to false to skip validation:

enum_field:speed,definitions,validatable: false

nullable

Controls whether nil values pass validation. Defaults to true. Set to false to require a value:

enum_field:speed,definitions,nullable: false

For polymorphic columns, nullability is derived from the association's optional flag rather than the global default. A belongs_to with optional: true allows nil; without it, nil is rejected. An explicit nullable option on the field still takes precedence:

classComment < ApplicationRecordbelongs_to:commentable,polymorphic: true,optional: true# nil allowed — derived from optional: trueenum_field:commentable_type,definitionsendclassAttachment < ApplicationRecordbelongs_to:attachable,polymorphic: true# nil rejected — association is required by defaultenum_field:attachable_type,definitions# Override: allow nil despite required associationenum_field:attachable_type,definitions,nullable: trueend

inquirable

Controls whether ? inquiry methods are generated. Defaults to true. Set to false to skip:

enum_field:speed,definitions,inquirable: false

Virtual Attributes

enum_field works with computed/virtual attributes that aren't backed by a database column. Define a method on the model and use scopeable: false and validatable: false since those features require a real column:

classSegment < ApplicationRecordenum_field:size_category,{small: {value: "small",label: "Small (< 100)",},medium: {value: "medium",label: "Medium (< 1K)",},large: {value: "large",label: "Large (< 10K)",},},scopeable: false,validatable: falsedefsize_categorycaseprofiles_countwhen ...100"small"when100...1_000"medium"else"large"endendend

All instance methods work as expected:

segment.size_category# "small"segment.size_category_label# "Small (< 100)"segment.size_category_metadata# { value: "small", label: "Small (< 100)" }segment.small_size_category?# true

Class methods (options, values, counts) also work normally:

Segment.size_category_options# [["Small (< 100)", "small"], ["Medium (< 1K)", "medium"], ...]Segment.size_category_values# ["small", "medium", "large"]

Custom Properties

You can add any custom properties to your definitions, and the gem will automatically create accessor methods for them:

classTicket < ApplicationRecordenum_field:priority,{low: {value: "low",label: "Low Priority",sla_hours: 72,notify_manager: false,},high: {value: "high",label: "High Priority",sla_hours: 4,notify_manager: true,},}end# Access custom properties directly via generated methodsticket.priority_sla_hours# 72ticket.priority_notify_manager# false# Or access via metadata hashticket.priority_metadata[:sla_hours]# 72ticket.priority_metadata[:notify_manager]# false

Registry & Standalone Registration

When enum_field is used in a model, definitions are automatically registered under a namespace derived from the model class name (e.g., Campaign becomes campaign).

You can also register definitions directly, outside of models, using the namespace DSL:

# config/initializers/enum_fields.rbEnumFields.namespace(:basic)doenum_field:priority,{low: {value: "low",label: "Low",},medium: {value: "medium",label: "Medium",},high: {value: "high",label: "High",},}enum_field:status,{active: {value: "active",label: "Active",},inactive: {value: "inactive",label: "Inactive",},}end

Access the raw registry:

EnumFields.registry# => { "basic" => { "priority" => { ... }, "status" => { ... } }, "campaign" => { ... } }

Catalog

EnumFields.catalog returns all registered definitions with namespaces sorted alphabetically, each field's entries as an array of metadata hashes (keys stripped):

EnumFields.catalog# => {# "basic" => {# "priority" => [# {# "value" => "low",# "label" => "Low",# },# {# "value" => "medium",# "label" => "Medium",# },# {# "value" => "high",# "label" => "High",# },# ],# "status" => [# {# "value" => "active",# "label" => "Active",# },# {# "value" => "inactive",# "label" => "Inactive",# },# ],# },# "campaign" => {# "stage" => [# {# "value" => "pending",# "label" => "Pending",# "icon" => "clock",# "color" => "yellow",# },# {# "value" => "processing",# "label" => "Processing",# "icon" => "cog",# "color" => "blue",# },# {# "value" => "shipped",# "label" => "Shipped",# "icon" => "truck",# "color" => "green",# },# {# "value" => "delivered",# "label" => "Delivered",# "icon" => "check",# "color" => "green",# },# ],# },# }

Development

After checking out the repo, run:

bundle install

Run the test suite:

bundle exec rspec

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/kinnell/enum_fields.

License

The gem is available as open source under the terms of the MIT License.

About

Enhanced enum-like fields for ActiveRecord models with metadata support

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages