Repository files navigation

ClassyEnum

Build StatusGem VersionCode ClimateDependency Status

ClassyEnum is a Ruby on Rails gem that adds class-based enumerator functionality to Active Record attributes.

This README is also available in a user-friendly DocumentUp format.

Rails & Ruby Versions Supported

Rails: 3.2.x - 4.2.x

Ruby: 1.9.3, 2.0.0, 2.1.x, 2.2.x, and 2.3.x

Installation

The gem is hosted at rubygems.org

Despite RailsGuides claiming that all directories under app will be autoloaded, I've had reports of this not being the case with newer versions of Ruby and Rails.

You may need to add the enums path to config/application.rb:

# Make sure classy_enum enums get loadedconfig.autoload_paths += %W(#{config.root}/app/enums)

Upgrading?

See the wiki for notes about upgrading from previous versions.

Getting Started & Example Usage

The most common use for ClassyEnum is to replace database lookup tables where the content and behavior is mostly static and has multiple "types". Please see the Wiki for a short discussion on use cases comparing ClassyEnum to other gems.

In this example, I have an Active Record model called Alarm with an attribute called priority. Priority is stored as a string (VARCHAR) type in the database and is converted to an enum value when requested.

1. Generate the Enum

The fastest way to get up and running with ClassyEnum is to use the built-in Rails generator like so:

rails generate classy_enum Priority low medium high

NOTE: You may destroy/revoke an enum by using the rails destroy command:

rails destroy classy_enum Priority

A new enum template file will be created at app/enums/priority.rb that will look like:

classPriority < ClassyEnum::BaseendclassPriority::Low < PriorityendclassPriority::Medium < PriorityendclassPriority::High < Priorityend

NOTE: The class order is important because it defines the enum member ordering as well as additional ClassyEnum behavior described below.

2. Customize the Enum

The generator creates a default setup, but each enum member can be changed to fit your needs.

I have defined three priority levels: low, medium, and high. Each priority level can have different properties and methods associated with it.

I would like to add a method called #send_email? that all member subclasses respond to. By default this method will return false, but will be overridden for high priority alarms to return true.

classPriority < ClassyEnum::Basedefsend_email?falseendendclassPriority::Low < PriorityendclassPriority::Medium < PriorityendclassPriority::High < Prioritydefsend_email?trueendend

3. Setup the Active Record model

My Active Record Alarm model needs a text field that will store a string representing the enum member. An example model schema might look something like:

create_table"alarms",force: truedo |t|
t.string"priority"t.boolean"enabled"end

NOTE: Alternatively, you may use an enum type if your database supports it. See this issue for more information.

Then in my model I've included ClassyEnum::ActiveRecord and added a line that calls classy_enum_attr with a single argument representing the enum I want to associate with my model. I am also delegating the #send_email? method to my Priority enum class.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:prioritydelegate:send_email?,to: :priorityend

With this setup, I can now do the following:

@alarm=Alarm.create(priority: :medium)@alarm.priority# => Priority::Medium@alarm.priority.medium?# => true@alarm.priority.high?# => false@alarm.priority.to_s# => 'medium'# Should this alarm send an email?@alarm.send_email?# => false@alarm.priority=:high@alarm.send_email?# => true

The enum field works like any other model attribute. It can be mass-assigned using #update_attributes.

What if your enum class name is not the same as your model's attribute name?

Just provide an optional class_name argument to declare the enum's class name. In this case, the model's attribute is called alarm_priority.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:alarm_priority,class_name: 'Priority'end@alarm=Alarm.create(alarm_priority: :medium)@alarm.alarm_priority# => Priority::Medium

Internationalization

ClassyEnum provides built-in support for translations using Ruby's I18n library. The translated values are provided via a #text method on each enum object. Translations are automatically applied when a key is found at locale.classy_enum.enum_parent_class.enum_value, or a default value is used that is equivalent to #to_s.titleize.

Given the following file config/locales/es.yml

es:
classy_enum:
priority:
low: 'Bajo'medium: 'Medio'high: 'Alto'

You can now do the following:

@alarm.priority=:low@alarm.priority.text# => 'Low'I18n.locale=:es@alarm.priority.text# => 'Bajo'

Using Enum as a Collection

ClassyEnum::Base extends the Enumerable module which provides several traversal and searching methods. This can be useful for situations where you are working with the collection, as opposed to the attributes on an Active Record object.

# Find the priority based on string or symbol:Priority.find(:low)# => Priority::Low.newPriority.find('medium')# => Priority::Medium.new# Test if a priority is valid:Priority.include?(:low)# => truePriority.include?(:lower)# => false# List priorities base strings:Priority.map{ |p| p.to_s}# => ["low", "medium", "high"]# Find the lowest priority that can send email:Priority.find(&:send_email?)# => Priority::High.new# Find the priorities that are lower than Priority::HighPriority.select{|p| p < :high}# => [Priority::Low.new, Priority::Medium.new]# Iterate over each priority:Priority.eachdo |priority|
putspriority.send_email?end

Default Enum Value

As with any Active Record attribute, default values can be specified in the database table and will propagate to new instances. However, there may be times when you can't or don't want to set the default value in the database. For these occasions, a default value can be specified like so:

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,default: 'medium'endAlarm.new.priority# => Priority::Medium

You may also use a Proc object to set the default value. The enum class is yielded to the block and can be used to determine the default at runtime.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,default: ->(enum){enum.max}endAlarm.new.priority# => Priority::High

Back Reference to Owning Object

In some cases you may want an enum class to reference the owning object (an instance of the Active Record model). Think of it as a belongs_to relationship, where the enum belongs to the model.

By default, the back reference can be called using #owner. If you want to refer to the owner by a different name, you must explicitly declare the owner name in the classy_enum parent class using the .owner class method.

Example using the default #owner method:

classPriority < ClassyEnum::Baseend# low and medium subclasses omittedclassPriority::High < Prioritydefsend_email?owner.enabled?endend

Example where the owner reference is explicitly declared:

classPriority < ClassyEnum::Baseowner:alarmend# low and medium subclasses omittedclassPriority::High < Prioritydefsend_email?alarm.enabled?endend

In the above examples, high priority alarms are only emailed if the owning alarm is enabled.

@alarm=Alarm.create(priority: :high,enabled: true)# Should this alarm send an email?@alarm.send_email?# => true@alarm.enabled=false@alarm.send_email?# => false

Model Validation

An Active Record validator validates_inclusion_of :field, in: ENUM is automatically added to your model when you use classy_enum_attr.

If your enum only has members low, medium, and high, then the following validation behavior would be expected:

@alarm=Alarm.new(priority: :really_high)@alarm.valid?# => false@alarm.priority=:high@alarm.valid?# => true

To allow nil or blank values, you can pass in :allow_nil and :allow_blank as options to classy_enum_attr:

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,allow_nil: trueend@alarm=Alarm.new(priority: nil)@alarm.valid?# => true

Active Record Querying

Classy Enum classes are plain Ruby objects, and Active Record does not know how to typecast them to strings when querying. Therefore you must explicitly convert the object to a string or symbol for the query to be valid.

Classy Enum versions before 4.0.0 supported querying directly with the enum objects. Suppport for this was removed in 4.0.0 because it depended on ARel internals, and maintaining backwards compatibility with older version of Rails was not possible.

Any of these are valid:

Alarm.where(priority: 'high')Alarm.where(priority: Priority[:high].to_s)Alarm.where(priority: Priority::High.new.to_s)

Note If you get an error like Cannot visit <Enum>, it means that ActiveRecord - or more accurately ARel - has received the Classy Enum class or instance directly, rather than a string representing corresponding to the database value. To resolve this issue, you need to convert the object to a string.

Form Usage

ClassyEnum includes a select_options helper method to generate an array of enum options that can be used by Rails' form builders such as SimpleForm and Formtastic.

# SimpleForm
<%= simple_form_for @alarm do |f| %><%= f.input :priority, as: :select, collection: Priority.select_options %><%= f.button :submit %><% end %>

Testing Enums

ClassyEnums can be tested by creating new instances of the ClassyEnum and testing expectations. For example:

classTestPriorityHigh < Minitest::Testdefsetup@priority_high_enum=Priority::High.newenddeftest_send_email_enabledassert@priority_high_enum.send_email?endend

If the ClassyEnum method implementations rely upon the owner, the ClassyEnum#build method can be used with the owner option. For example:

classTestPriorityHigh < Minitest::Testdefsetup@alarm=Alarm.create@priority_high_enum=Priority::High.build(:high,owner: @alarm)enddeftest_send_email_enabledassert_equal@priority_high_enum.owner,@alarmendend

Copyright

Copyright (c) 2010-2016 Peter Brown. See LICENSE for details.

About

A class-based enumerator gem for Rails

Resources

Contributing

Stars

273 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

ClassyEnum

Build StatusGem VersionCode ClimateDependency Status

ClassyEnum is a Ruby on Rails gem that adds class-based enumerator functionality to Active Record attributes.

This README is also available in a user-friendly DocumentUp format.

Rails & Ruby Versions Supported

Rails: 3.2.x - 4.2.x

Ruby: 1.9.3, 2.0.0, 2.1.x, 2.2.x, and 2.3.x

Installation

The gem is hosted at rubygems.org

Despite RailsGuides claiming that all directories under app will be autoloaded, I've had reports of this not being the case with newer versions of Ruby and Rails.

You may need to add the enums path to config/application.rb:

# Make sure classy_enum enums get loadedconfig.autoload_paths += %W(#{config.root}/app/enums)

Upgrading?

See the wiki for notes about upgrading from previous versions.

Getting Started & Example Usage

The most common use for ClassyEnum is to replace database lookup tables where the content and behavior is mostly static and has multiple "types". Please see the Wiki for a short discussion on use cases comparing ClassyEnum to other gems.

In this example, I have an Active Record model called Alarm with an attribute called priority. Priority is stored as a string (VARCHAR) type in the database and is converted to an enum value when requested.

1. Generate the Enum

The fastest way to get up and running with ClassyEnum is to use the built-in Rails generator like so:

rails generate classy_enum Priority low medium high

NOTE: You may destroy/revoke an enum by using the rails destroy command:

rails destroy classy_enum Priority

A new enum template file will be created at app/enums/priority.rb that will look like:

classPriority < ClassyEnum::BaseendclassPriority::Low < PriorityendclassPriority::Medium < PriorityendclassPriority::High < Priorityend

NOTE: The class order is important because it defines the enum member ordering as well as additional ClassyEnum behavior described below.

2. Customize the Enum

The generator creates a default setup, but each enum member can be changed to fit your needs.

I have defined three priority levels: low, medium, and high. Each priority level can have different properties and methods associated with it.

I would like to add a method called #send_email? that all member subclasses respond to. By default this method will return false, but will be overridden for high priority alarms to return true.

classPriority < ClassyEnum::Basedefsend_email?falseendendclassPriority::Low < PriorityendclassPriority::Medium < PriorityendclassPriority::High < Prioritydefsend_email?trueendend

3. Setup the Active Record model

My Active Record Alarm model needs a text field that will store a string representing the enum member. An example model schema might look something like:

create_table"alarms",force: truedo |t|
t.string"priority"t.boolean"enabled"end

NOTE: Alternatively, you may use an enum type if your database supports it. See this issue for more information.

Then in my model I've included ClassyEnum::ActiveRecord and added a line that calls classy_enum_attr with a single argument representing the enum I want to associate with my model. I am also delegating the #send_email? method to my Priority enum class.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:prioritydelegate:send_email?,to: :priorityend

With this setup, I can now do the following:

@alarm=Alarm.create(priority: :medium)@alarm.priority# => Priority::Medium@alarm.priority.medium?# => true@alarm.priority.high?# => false@alarm.priority.to_s# => 'medium'# Should this alarm send an email?@alarm.send_email?# => false@alarm.priority=:high@alarm.send_email?# => true

The enum field works like any other model attribute. It can be mass-assigned using #update_attributes.

What if your enum class name is not the same as your model's attribute name?

Just provide an optional class_name argument to declare the enum's class name. In this case, the model's attribute is called alarm_priority.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:alarm_priority,class_name: 'Priority'end@alarm=Alarm.create(alarm_priority: :medium)@alarm.alarm_priority# => Priority::Medium

Internationalization

ClassyEnum provides built-in support for translations using Ruby's I18n library. The translated values are provided via a #text method on each enum object. Translations are automatically applied when a key is found at locale.classy_enum.enum_parent_class.enum_value, or a default value is used that is equivalent to #to_s.titleize.

Given the following file config/locales/es.yml

es:
classy_enum:
priority:
low: 'Bajo'medium: 'Medio'high: 'Alto'

You can now do the following:

@alarm.priority=:low@alarm.priority.text# => 'Low'I18n.locale=:es@alarm.priority.text# => 'Bajo'

Using Enum as a Collection

ClassyEnum::Base extends the Enumerable module which provides several traversal and searching methods. This can be useful for situations where you are working with the collection, as opposed to the attributes on an Active Record object.

# Find the priority based on string or symbol:Priority.find(:low)# => Priority::Low.newPriority.find('medium')# => Priority::Medium.new# Test if a priority is valid:Priority.include?(:low)# => truePriority.include?(:lower)# => false# List priorities base strings:Priority.map{ |p| p.to_s}# => ["low", "medium", "high"]# Find the lowest priority that can send email:Priority.find(&:send_email?)# => Priority::High.new# Find the priorities that are lower than Priority::HighPriority.select{|p| p < :high}# => [Priority::Low.new, Priority::Medium.new]# Iterate over each priority:Priority.eachdo |priority|
putspriority.send_email?end

Default Enum Value

As with any Active Record attribute, default values can be specified in the database table and will propagate to new instances. However, there may be times when you can't or don't want to set the default value in the database. For these occasions, a default value can be specified like so:

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,default: 'medium'endAlarm.new.priority# => Priority::Medium

You may also use a Proc object to set the default value. The enum class is yielded to the block and can be used to determine the default at runtime.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,default: ->(enum){enum.max}endAlarm.new.priority# => Priority::High

Back Reference to Owning Object

In some cases you may want an enum class to reference the owning object (an instance of the Active Record model). Think of it as a belongs_to relationship, where the enum belongs to the model.

By default, the back reference can be called using #owner. If you want to refer to the owner by a different name, you must explicitly declare the owner name in the classy_enum parent class using the .owner class method.

Example using the default #owner method:

classPriority < ClassyEnum::Baseend# low and medium subclasses omittedclassPriority::High < Prioritydefsend_email?owner.enabled?endend

Example where the owner reference is explicitly declared:

classPriority < ClassyEnum::Baseowner:alarmend# low and medium subclasses omittedclassPriority::High < Prioritydefsend_email?alarm.enabled?endend

In the above examples, high priority alarms are only emailed if the owning alarm is enabled.

@alarm=Alarm.create(priority: :high,enabled: true)# Should this alarm send an email?@alarm.send_email?# => true@alarm.enabled=false@alarm.send_email?# => false

Model Validation

An Active Record validator validates_inclusion_of :field, in: ENUM is automatically added to your model when you use classy_enum_attr.

If your enum only has members low, medium, and high, then the following validation behavior would be expected:

@alarm=Alarm.new(priority: :really_high)@alarm.valid?# => false@alarm.priority=:high@alarm.valid?# => true

To allow nil or blank values, you can pass in :allow_nil and :allow_blank as options to classy_enum_attr:

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,allow_nil: trueend@alarm=Alarm.new(priority: nil)@alarm.valid?# => true

Active Record Querying

Classy Enum classes are plain Ruby objects, and Active Record does not know how to typecast them to strings when querying. Therefore you must explicitly convert the object to a string or symbol for the query to be valid.

Classy Enum versions before 4.0.0 supported querying directly with the enum objects. Suppport for this was removed in 4.0.0 because it depended on ARel internals, and maintaining backwards compatibility with older version of Rails was not possible.

Any of these are valid:

Alarm.where(priority: 'high')Alarm.where(priority: Priority[:high].to_s)Alarm.where(priority: Priority::High.new.to_s)

Note If you get an error like Cannot visit <Enum>, it means that ActiveRecord - or more accurately ARel - has received the Classy Enum class or instance directly, rather than a string representing corresponding to the database value. To resolve this issue, you need to convert the object to a string.

Form Usage

ClassyEnum includes a select_options helper method to generate an array of enum options that can be used by Rails' form builders such as SimpleForm and Formtastic.

# SimpleForm
<%= simple_form_for @alarm do |f| %><%= f.input :priority, as: :select, collection: Priority.select_options %><%= f.button :submit %><% end %>

Testing Enums

ClassyEnums can be tested by creating new instances of the ClassyEnum and testing expectations. For example:

classTestPriorityHigh < Minitest::Testdefsetup@priority_high_enum=Priority::High.newenddeftest_send_email_enabledassert@priority_high_enum.send_email?endend

If the ClassyEnum method implementations rely upon the owner, the ClassyEnum#build method can be used with the owner option. For example:

classTestPriorityHigh < Minitest::Testdefsetup@alarm=Alarm.create@priority_high_enum=Priority::High.build(:high,owner: @alarm)enddeftest_send_email_enabledassert_equal@priority_high_enum.owner,@alarmendend

Copyright

Copyright (c) 2010-2016 Peter Brown. See LICENSE for details.

About

A class-based enumerator gem for Rails

Resources

Contributing

Stars

273 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

ClassyEnum

Build StatusGem VersionCode ClimateDependency Status

ClassyEnum is a Ruby on Rails gem that adds class-based enumerator functionality to Active Record attributes.

This README is also available in a user-friendly DocumentUp format.

Rails & Ruby Versions Supported

Rails: 3.2.x - 4.2.x

Ruby: 1.9.3, 2.0.0, 2.1.x, 2.2.x, and 2.3.x

Installation

The gem is hosted at rubygems.org

Despite RailsGuides claiming that all directories under app will be autoloaded, I've had reports of this not being the case with newer versions of Ruby and Rails.

You may need to add the enums path to config/application.rb:

# Make sure classy_enum enums get loadedconfig.autoload_paths += %W(#{config.root}/app/enums)

Upgrading?

See the wiki for notes about upgrading from previous versions.

Getting Started & Example Usage

The most common use for ClassyEnum is to replace database lookup tables where the content and behavior is mostly static and has multiple "types". Please see the Wiki for a short discussion on use cases comparing ClassyEnum to other gems.

In this example, I have an Active Record model called Alarm with an attribute called priority. Priority is stored as a string (VARCHAR) type in the database and is converted to an enum value when requested.

1. Generate the Enum

The fastest way to get up and running with ClassyEnum is to use the built-in Rails generator like so:

rails generate classy_enum Priority low medium high

NOTE: You may destroy/revoke an enum by using the rails destroy command:

rails destroy classy_enum Priority

A new enum template file will be created at app/enums/priority.rb that will look like:

classPriority < ClassyEnum::BaseendclassPriority::Low < PriorityendclassPriority::Medium < PriorityendclassPriority::High < Priorityend

NOTE: The class order is important because it defines the enum member ordering as well as additional ClassyEnum behavior described below.

2. Customize the Enum

The generator creates a default setup, but each enum member can be changed to fit your needs.

I have defined three priority levels: low, medium, and high. Each priority level can have different properties and methods associated with it.

I would like to add a method called #send_email? that all member subclasses respond to. By default this method will return false, but will be overridden for high priority alarms to return true.

classPriority < ClassyEnum::Basedefsend_email?falseendendclassPriority::Low < PriorityendclassPriority::Medium < PriorityendclassPriority::High < Prioritydefsend_email?trueendend

3. Setup the Active Record model

My Active Record Alarm model needs a text field that will store a string representing the enum member. An example model schema might look something like:

create_table"alarms",force: truedo |t|
t.string"priority"t.boolean"enabled"end

NOTE: Alternatively, you may use an enum type if your database supports it. See this issue for more information.

Then in my model I've included ClassyEnum::ActiveRecord and added a line that calls classy_enum_attr with a single argument representing the enum I want to associate with my model. I am also delegating the #send_email? method to my Priority enum class.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:prioritydelegate:send_email?,to: :priorityend

With this setup, I can now do the following:

@alarm=Alarm.create(priority: :medium)@alarm.priority# => Priority::Medium@alarm.priority.medium?# => true@alarm.priority.high?# => false@alarm.priority.to_s# => 'medium'# Should this alarm send an email?@alarm.send_email?# => false@alarm.priority=:high@alarm.send_email?# => true

The enum field works like any other model attribute. It can be mass-assigned using #update_attributes.

What if your enum class name is not the same as your model's attribute name?

Just provide an optional class_name argument to declare the enum's class name. In this case, the model's attribute is called alarm_priority.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:alarm_priority,class_name: 'Priority'end@alarm=Alarm.create(alarm_priority: :medium)@alarm.alarm_priority# => Priority::Medium

Internationalization

ClassyEnum provides built-in support for translations using Ruby's I18n library. The translated values are provided via a #text method on each enum object. Translations are automatically applied when a key is found at locale.classy_enum.enum_parent_class.enum_value, or a default value is used that is equivalent to #to_s.titleize.

Given the following file config/locales/es.yml

es:
classy_enum:
priority:
low: 'Bajo'medium: 'Medio'high: 'Alto'

You can now do the following:

@alarm.priority=:low@alarm.priority.text# => 'Low'I18n.locale=:es@alarm.priority.text# => 'Bajo'

Using Enum as a Collection

ClassyEnum::Base extends the Enumerable module which provides several traversal and searching methods. This can be useful for situations where you are working with the collection, as opposed to the attributes on an Active Record object.

# Find the priority based on string or symbol:Priority.find(:low)# => Priority::Low.newPriority.find('medium')# => Priority::Medium.new# Test if a priority is valid:Priority.include?(:low)# => truePriority.include?(:lower)# => false# List priorities base strings:Priority.map{ |p| p.to_s}# => ["low", "medium", "high"]# Find the lowest priority that can send email:Priority.find(&:send_email?)# => Priority::High.new# Find the priorities that are lower than Priority::HighPriority.select{|p| p < :high}# => [Priority::Low.new, Priority::Medium.new]# Iterate over each priority:Priority.eachdo |priority|
putspriority.send_email?end

Default Enum Value

As with any Active Record attribute, default values can be specified in the database table and will propagate to new instances. However, there may be times when you can't or don't want to set the default value in the database. For these occasions, a default value can be specified like so:

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,default: 'medium'endAlarm.new.priority# => Priority::Medium

You may also use a Proc object to set the default value. The enum class is yielded to the block and can be used to determine the default at runtime.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,default: ->(enum){enum.max}endAlarm.new.priority# => Priority::High

Back Reference to Owning Object

In some cases you may want an enum class to reference the owning object (an instance of the Active Record model). Think of it as a belongs_to relationship, where the enum belongs to the model.

By default, the back reference can be called using #owner. If you want to refer to the owner by a different name, you must explicitly declare the owner name in the classy_enum parent class using the .owner class method.

Example using the default #owner method:

classPriority < ClassyEnum::Baseend# low and medium subclasses omittedclassPriority::High < Prioritydefsend_email?owner.enabled?endend

Example where the owner reference is explicitly declared:

classPriority < ClassyEnum::Baseowner:alarmend# low and medium subclasses omittedclassPriority::High < Prioritydefsend_email?alarm.enabled?endend

In the above examples, high priority alarms are only emailed if the owning alarm is enabled.

@alarm=Alarm.create(priority: :high,enabled: true)# Should this alarm send an email?@alarm.send_email?# => true@alarm.enabled=false@alarm.send_email?# => false

Model Validation

An Active Record validator validates_inclusion_of :field, in: ENUM is automatically added to your model when you use classy_enum_attr.

If your enum only has members low, medium, and high, then the following validation behavior would be expected:

@alarm=Alarm.new(priority: :really_high)@alarm.valid?# => false@alarm.priority=:high@alarm.valid?# => true

To allow nil or blank values, you can pass in :allow_nil and :allow_blank as options to classy_enum_attr:

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,allow_nil: trueend@alarm=Alarm.new(priority: nil)@alarm.valid?# => true

Active Record Querying

Classy Enum classes are plain Ruby objects, and Active Record does not know how to typecast them to strings when querying. Therefore you must explicitly convert the object to a string or symbol for the query to be valid.

Classy Enum versions before 4.0.0 supported querying directly with the enum objects. Suppport for this was removed in 4.0.0 because it depended on ARel internals, and maintaining backwards compatibility with older version of Rails was not possible.

Any of these are valid:

Alarm.where(priority: 'high')Alarm.where(priority: Priority[:high].to_s)Alarm.where(priority: Priority::High.new.to_s)

Note If you get an error like Cannot visit <Enum>, it means that ActiveRecord - or more accurately ARel - has received the Classy Enum class or instance directly, rather than a string representing corresponding to the database value. To resolve this issue, you need to convert the object to a string.

Form Usage

ClassyEnum includes a select_options helper method to generate an array of enum options that can be used by Rails' form builders such as SimpleForm and Formtastic.

# SimpleForm
<%= simple_form_for @alarm do |f| %><%= f.input :priority, as: :select, collection: Priority.select_options %><%= f.button :submit %><% end %>

Testing Enums

ClassyEnums can be tested by creating new instances of the ClassyEnum and testing expectations. For example:

classTestPriorityHigh < Minitest::Testdefsetup@priority_high_enum=Priority::High.newenddeftest_send_email_enabledassert@priority_high_enum.send_email?endend

If the ClassyEnum method implementations rely upon the owner, the ClassyEnum#build method can be used with the owner option. For example:

classTestPriorityHigh < Minitest::Testdefsetup@alarm=Alarm.create@priority_high_enum=Priority::High.build(:high,owner: @alarm)enddeftest_send_email_enabledassert_equal@priority_high_enum.owner,@alarmendend

Copyright

Copyright (c) 2010-2016 Peter Brown. See LICENSE for details.

About

A class-based enumerator gem for Rails

Resources

Contributing

Stars

273 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

ClassyEnum

Build StatusGem VersionCode ClimateDependency Status

ClassyEnum is a Ruby on Rails gem that adds class-based enumerator functionality to Active Record attributes.

This README is also available in a user-friendly DocumentUp format.

Rails & Ruby Versions Supported

Rails: 3.2.x - 4.2.x

Ruby: 1.9.3, 2.0.0, 2.1.x, 2.2.x, and 2.3.x

Installation

The gem is hosted at rubygems.org

Despite RailsGuides claiming that all directories under app will be autoloaded, I've had reports of this not being the case with newer versions of Ruby and Rails.

You may need to add the enums path to config/application.rb:

# Make sure classy_enum enums get loadedconfig.autoload_paths += %W(#{config.root}/app/enums)

Upgrading?

See the wiki for notes about upgrading from previous versions.

Getting Started & Example Usage

The most common use for ClassyEnum is to replace database lookup tables where the content and behavior is mostly static and has multiple "types". Please see the Wiki for a short discussion on use cases comparing ClassyEnum to other gems.

In this example, I have an Active Record model called Alarm with an attribute called priority. Priority is stored as a string (VARCHAR) type in the database and is converted to an enum value when requested.

1. Generate the Enum

The fastest way to get up and running with ClassyEnum is to use the built-in Rails generator like so:

rails generate classy_enum Priority low medium high

NOTE: You may destroy/revoke an enum by using the rails destroy command:

rails destroy classy_enum Priority

A new enum template file will be created at app/enums/priority.rb that will look like:

classPriority < ClassyEnum::BaseendclassPriority::Low < PriorityendclassPriority::Medium < PriorityendclassPriority::High < Priorityend

NOTE: The class order is important because it defines the enum member ordering as well as additional ClassyEnum behavior described below.

2. Customize the Enum

The generator creates a default setup, but each enum member can be changed to fit your needs.

I have defined three priority levels: low, medium, and high. Each priority level can have different properties and methods associated with it.

I would like to add a method called #send_email? that all member subclasses respond to. By default this method will return false, but will be overridden for high priority alarms to return true.

classPriority < ClassyEnum::Basedefsend_email?falseendendclassPriority::Low < PriorityendclassPriority::Medium < PriorityendclassPriority::High < Prioritydefsend_email?trueendend

3. Setup the Active Record model

My Active Record Alarm model needs a text field that will store a string representing the enum member. An example model schema might look something like:

create_table"alarms",force: truedo |t|
t.string"priority"t.boolean"enabled"end

NOTE: Alternatively, you may use an enum type if your database supports it. See this issue for more information.

Then in my model I've included ClassyEnum::ActiveRecord and added a line that calls classy_enum_attr with a single argument representing the enum I want to associate with my model. I am also delegating the #send_email? method to my Priority enum class.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:prioritydelegate:send_email?,to: :priorityend

With this setup, I can now do the following:

@alarm=Alarm.create(priority: :medium)@alarm.priority# => Priority::Medium@alarm.priority.medium?# => true@alarm.priority.high?# => false@alarm.priority.to_s# => 'medium'# Should this alarm send an email?@alarm.send_email?# => false@alarm.priority=:high@alarm.send_email?# => true

The enum field works like any other model attribute. It can be mass-assigned using #update_attributes.

What if your enum class name is not the same as your model's attribute name?

Just provide an optional class_name argument to declare the enum's class name. In this case, the model's attribute is called alarm_priority.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:alarm_priority,class_name: 'Priority'end@alarm=Alarm.create(alarm_priority: :medium)@alarm.alarm_priority# => Priority::Medium

Internationalization

ClassyEnum provides built-in support for translations using Ruby's I18n library. The translated values are provided via a #text method on each enum object. Translations are automatically applied when a key is found at locale.classy_enum.enum_parent_class.enum_value, or a default value is used that is equivalent to #to_s.titleize.

Given the following file config/locales/es.yml

es:
classy_enum:
priority:
low: 'Bajo'medium: 'Medio'high: 'Alto'

You can now do the following:

@alarm.priority=:low@alarm.priority.text# => 'Low'I18n.locale=:es@alarm.priority.text# => 'Bajo'

Using Enum as a Collection

ClassyEnum::Base extends the Enumerable module which provides several traversal and searching methods. This can be useful for situations where you are working with the collection, as opposed to the attributes on an Active Record object.

# Find the priority based on string or symbol:Priority.find(:low)# => Priority::Low.newPriority.find('medium')# => Priority::Medium.new# Test if a priority is valid:Priority.include?(:low)# => truePriority.include?(:lower)# => false# List priorities base strings:Priority.map{ |p| p.to_s}# => ["low", "medium", "high"]# Find the lowest priority that can send email:Priority.find(&:send_email?)# => Priority::High.new# Find the priorities that are lower than Priority::HighPriority.select{|p| p < :high}# => [Priority::Low.new, Priority::Medium.new]# Iterate over each priority:Priority.eachdo |priority|
putspriority.send_email?end

Default Enum Value

As with any Active Record attribute, default values can be specified in the database table and will propagate to new instances. However, there may be times when you can't or don't want to set the default value in the database. For these occasions, a default value can be specified like so:

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,default: 'medium'endAlarm.new.priority# => Priority::Medium

You may also use a Proc object to set the default value. The enum class is yielded to the block and can be used to determine the default at runtime.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,default: ->(enum){enum.max}endAlarm.new.priority# => Priority::High

Back Reference to Owning Object

In some cases you may want an enum class to reference the owning object (an instance of the Active Record model). Think of it as a belongs_to relationship, where the enum belongs to the model.

By default, the back reference can be called using #owner. If you want to refer to the owner by a different name, you must explicitly declare the owner name in the classy_enum parent class using the .owner class method.

Example using the default #owner method:

classPriority < ClassyEnum::Baseend# low and medium subclasses omittedclassPriority::High < Prioritydefsend_email?owner.enabled?endend

Example where the owner reference is explicitly declared:

classPriority < ClassyEnum::Baseowner:alarmend# low and medium subclasses omittedclassPriority::High < Prioritydefsend_email?alarm.enabled?endend

In the above examples, high priority alarms are only emailed if the owning alarm is enabled.

@alarm=Alarm.create(priority: :high,enabled: true)# Should this alarm send an email?@alarm.send_email?# => true@alarm.enabled=false@alarm.send_email?# => false

Model Validation

An Active Record validator validates_inclusion_of :field, in: ENUM is automatically added to your model when you use classy_enum_attr.

If your enum only has members low, medium, and high, then the following validation behavior would be expected:

@alarm=Alarm.new(priority: :really_high)@alarm.valid?# => false@alarm.priority=:high@alarm.valid?# => true

To allow nil or blank values, you can pass in :allow_nil and :allow_blank as options to classy_enum_attr:

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,allow_nil: trueend@alarm=Alarm.new(priority: nil)@alarm.valid?# => true

Active Record Querying

Classy Enum classes are plain Ruby objects, and Active Record does not know how to typecast them to strings when querying. Therefore you must explicitly convert the object to a string or symbol for the query to be valid.

Classy Enum versions before 4.0.0 supported querying directly with the enum objects. Suppport for this was removed in 4.0.0 because it depended on ARel internals, and maintaining backwards compatibility with older version of Rails was not possible.

Any of these are valid:

Alarm.where(priority: 'high')Alarm.where(priority: Priority[:high].to_s)Alarm.where(priority: Priority::High.new.to_s)

Note If you get an error like Cannot visit <Enum>, it means that ActiveRecord - or more accurately ARel - has received the Classy Enum class or instance directly, rather than a string representing corresponding to the database value. To resolve this issue, you need to convert the object to a string.

Form Usage

ClassyEnum includes a select_options helper method to generate an array of enum options that can be used by Rails' form builders such as SimpleForm and Formtastic.

# SimpleForm
<%= simple_form_for @alarm do |f| %><%= f.input :priority, as: :select, collection: Priority.select_options %><%= f.button :submit %><% end %>

Testing Enums

ClassyEnums can be tested by creating new instances of the ClassyEnum and testing expectations. For example:

classTestPriorityHigh < Minitest::Testdefsetup@priority_high_enum=Priority::High.newenddeftest_send_email_enabledassert@priority_high_enum.send_email?endend

If the ClassyEnum method implementations rely upon the owner, the ClassyEnum#build method can be used with the owner option. For example:

classTestPriorityHigh < Minitest::Testdefsetup@alarm=Alarm.create@priority_high_enum=Priority::High.build(:high,owner: @alarm)enddeftest_send_email_enabledassert_equal@priority_high_enum.owner,@alarmendend

Copyright

Copyright (c) 2010-2016 Peter Brown. See LICENSE for details.

About

A class-based enumerator gem for Rails

Resources

Contributing

Stars

273 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

ClassyEnum

Build StatusGem VersionCode ClimateDependency Status

ClassyEnum is a Ruby on Rails gem that adds class-based enumerator functionality to Active Record attributes.

This README is also available in a user-friendly DocumentUp format.

Rails & Ruby Versions Supported

Rails: 3.2.x - 4.2.x

Ruby: 1.9.3, 2.0.0, 2.1.x, 2.2.x, and 2.3.x

Installation

The gem is hosted at rubygems.org

Despite RailsGuides claiming that all directories under app will be autoloaded, I've had reports of this not being the case with newer versions of Ruby and Rails.

You may need to add the enums path to config/application.rb:

# Make sure classy_enum enums get loadedconfig.autoload_paths += %W(#{config.root}/app/enums)

Upgrading?

See the wiki for notes about upgrading from previous versions.

Getting Started & Example Usage

The most common use for ClassyEnum is to replace database lookup tables where the content and behavior is mostly static and has multiple "types". Please see the Wiki for a short discussion on use cases comparing ClassyEnum to other gems.

In this example, I have an Active Record model called Alarm with an attribute called priority. Priority is stored as a string (VARCHAR) type in the database and is converted to an enum value when requested.

1. Generate the Enum

The fastest way to get up and running with ClassyEnum is to use the built-in Rails generator like so:

rails generate classy_enum Priority low medium high

NOTE: You may destroy/revoke an enum by using the rails destroy command:

rails destroy classy_enum Priority

A new enum template file will be created at app/enums/priority.rb that will look like:

classPriority < ClassyEnum::BaseendclassPriority::Low < PriorityendclassPriority::Medium < PriorityendclassPriority::High < Priorityend

NOTE: The class order is important because it defines the enum member ordering as well as additional ClassyEnum behavior described below.

2. Customize the Enum

The generator creates a default setup, but each enum member can be changed to fit your needs.

I have defined three priority levels: low, medium, and high. Each priority level can have different properties and methods associated with it.

I would like to add a method called #send_email? that all member subclasses respond to. By default this method will return false, but will be overridden for high priority alarms to return true.

classPriority < ClassyEnum::Basedefsend_email?falseendendclassPriority::Low < PriorityendclassPriority::Medium < PriorityendclassPriority::High < Prioritydefsend_email?trueendend

3. Setup the Active Record model

My Active Record Alarm model needs a text field that will store a string representing the enum member. An example model schema might look something like:

create_table"alarms",force: truedo |t|
t.string"priority"t.boolean"enabled"end

NOTE: Alternatively, you may use an enum type if your database supports it. See this issue for more information.

Then in my model I've included ClassyEnum::ActiveRecord and added a line that calls classy_enum_attr with a single argument representing the enum I want to associate with my model. I am also delegating the #send_email? method to my Priority enum class.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:prioritydelegate:send_email?,to: :priorityend

With this setup, I can now do the following:

@alarm=Alarm.create(priority: :medium)@alarm.priority# => Priority::Medium@alarm.priority.medium?# => true@alarm.priority.high?# => false@alarm.priority.to_s# => 'medium'# Should this alarm send an email?@alarm.send_email?# => false@alarm.priority=:high@alarm.send_email?# => true

The enum field works like any other model attribute. It can be mass-assigned using #update_attributes.

What if your enum class name is not the same as your model's attribute name?

Just provide an optional class_name argument to declare the enum's class name. In this case, the model's attribute is called alarm_priority.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:alarm_priority,class_name: 'Priority'end@alarm=Alarm.create(alarm_priority: :medium)@alarm.alarm_priority# => Priority::Medium

Internationalization

ClassyEnum provides built-in support for translations using Ruby's I18n library. The translated values are provided via a #text method on each enum object. Translations are automatically applied when a key is found at locale.classy_enum.enum_parent_class.enum_value, or a default value is used that is equivalent to #to_s.titleize.

Given the following file config/locales/es.yml

es:
classy_enum:
priority:
low: 'Bajo'medium: 'Medio'high: 'Alto'

You can now do the following:

@alarm.priority=:low@alarm.priority.text# => 'Low'I18n.locale=:es@alarm.priority.text# => 'Bajo'

Using Enum as a Collection

ClassyEnum::Base extends the Enumerable module which provides several traversal and searching methods. This can be useful for situations where you are working with the collection, as opposed to the attributes on an Active Record object.

# Find the priority based on string or symbol:Priority.find(:low)# => Priority::Low.newPriority.find('medium')# => Priority::Medium.new# Test if a priority is valid:Priority.include?(:low)# => truePriority.include?(:lower)# => false# List priorities base strings:Priority.map{ |p| p.to_s}# => ["low", "medium", "high"]# Find the lowest priority that can send email:Priority.find(&:send_email?)# => Priority::High.new# Find the priorities that are lower than Priority::HighPriority.select{|p| p < :high}# => [Priority::Low.new, Priority::Medium.new]# Iterate over each priority:Priority.eachdo |priority|
putspriority.send_email?end

Default Enum Value

As with any Active Record attribute, default values can be specified in the database table and will propagate to new instances. However, there may be times when you can't or don't want to set the default value in the database. For these occasions, a default value can be specified like so:

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,default: 'medium'endAlarm.new.priority# => Priority::Medium

You may also use a Proc object to set the default value. The enum class is yielded to the block and can be used to determine the default at runtime.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,default: ->(enum){enum.max}endAlarm.new.priority# => Priority::High

Back Reference to Owning Object

In some cases you may want an enum class to reference the owning object (an instance of the Active Record model). Think of it as a belongs_to relationship, where the enum belongs to the model.

By default, the back reference can be called using #owner. If you want to refer to the owner by a different name, you must explicitly declare the owner name in the classy_enum parent class using the .owner class method.

Example using the default #owner method:

classPriority < ClassyEnum::Baseend# low and medium subclasses omittedclassPriority::High < Prioritydefsend_email?owner.enabled?endend

Example where the owner reference is explicitly declared:

classPriority < ClassyEnum::Baseowner:alarmend# low and medium subclasses omittedclassPriority::High < Prioritydefsend_email?alarm.enabled?endend

In the above examples, high priority alarms are only emailed if the owning alarm is enabled.

@alarm=Alarm.create(priority: :high,enabled: true)# Should this alarm send an email?@alarm.send_email?# => true@alarm.enabled=false@alarm.send_email?# => false

Model Validation

An Active Record validator validates_inclusion_of :field, in: ENUM is automatically added to your model when you use classy_enum_attr.

If your enum only has members low, medium, and high, then the following validation behavior would be expected:

@alarm=Alarm.new(priority: :really_high)@alarm.valid?# => false@alarm.priority=:high@alarm.valid?# => true

To allow nil or blank values, you can pass in :allow_nil and :allow_blank as options to classy_enum_attr:

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,allow_nil: trueend@alarm=Alarm.new(priority: nil)@alarm.valid?# => true

Active Record Querying

Classy Enum classes are plain Ruby objects, and Active Record does not know how to typecast them to strings when querying. Therefore you must explicitly convert the object to a string or symbol for the query to be valid.

Classy Enum versions before 4.0.0 supported querying directly with the enum objects. Suppport for this was removed in 4.0.0 because it depended on ARel internals, and maintaining backwards compatibility with older version of Rails was not possible.

Any of these are valid:

Alarm.where(priority: 'high')Alarm.where(priority: Priority[:high].to_s)Alarm.where(priority: Priority::High.new.to_s)

Note If you get an error like Cannot visit <Enum>, it means that ActiveRecord - or more accurately ARel - has received the Classy Enum class or instance directly, rather than a string representing corresponding to the database value. To resolve this issue, you need to convert the object to a string.

Form Usage

ClassyEnum includes a select_options helper method to generate an array of enum options that can be used by Rails' form builders such as SimpleForm and Formtastic.

# SimpleForm
<%= simple_form_for @alarm do |f| %><%= f.input :priority, as: :select, collection: Priority.select_options %><%= f.button :submit %><% end %>

Testing Enums

ClassyEnums can be tested by creating new instances of the ClassyEnum and testing expectations. For example:

classTestPriorityHigh < Minitest::Testdefsetup@priority_high_enum=Priority::High.newenddeftest_send_email_enabledassert@priority_high_enum.send_email?endend

If the ClassyEnum method implementations rely upon the owner, the ClassyEnum#build method can be used with the owner option. For example:

classTestPriorityHigh < Minitest::Testdefsetup@alarm=Alarm.create@priority_high_enum=Priority::High.build(:high,owner: @alarm)enddeftest_send_email_enabledassert_equal@priority_high_enum.owner,@alarmendend

Copyright

Copyright (c) 2010-2016 Peter Brown. See LICENSE for details.

About

A class-based enumerator gem for Rails

Resources

Contributing

Stars

273 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

ClassyEnum

Build StatusGem VersionCode ClimateDependency Status

ClassyEnum is a Ruby on Rails gem that adds class-based enumerator functionality to Active Record attributes.

This README is also available in a user-friendly DocumentUp format.

Rails & Ruby Versions Supported

Rails: 3.2.x - 4.2.x

Ruby: 1.9.3, 2.0.0, 2.1.x, 2.2.x, and 2.3.x

Installation

The gem is hosted at rubygems.org

Despite RailsGuides claiming that all directories under app will be autoloaded, I've had reports of this not being the case with newer versions of Ruby and Rails.

You may need to add the enums path to config/application.rb:

# Make sure classy_enum enums get loadedconfig.autoload_paths += %W(#{config.root}/app/enums)

Upgrading?

See the wiki for notes about upgrading from previous versions.

Getting Started & Example Usage

The most common use for ClassyEnum is to replace database lookup tables where the content and behavior is mostly static and has multiple "types". Please see the Wiki for a short discussion on use cases comparing ClassyEnum to other gems.

In this example, I have an Active Record model called Alarm with an attribute called priority. Priority is stored as a string (VARCHAR) type in the database and is converted to an enum value when requested.

1. Generate the Enum

The fastest way to get up and running with ClassyEnum is to use the built-in Rails generator like so:

rails generate classy_enum Priority low medium high

NOTE: You may destroy/revoke an enum by using the rails destroy command:

rails destroy classy_enum Priority

A new enum template file will be created at app/enums/priority.rb that will look like:

classPriority < ClassyEnum::BaseendclassPriority::Low < PriorityendclassPriority::Medium < PriorityendclassPriority::High < Priorityend

NOTE: The class order is important because it defines the enum member ordering as well as additional ClassyEnum behavior described below.

2. Customize the Enum

The generator creates a default setup, but each enum member can be changed to fit your needs.

I have defined three priority levels: low, medium, and high. Each priority level can have different properties and methods associated with it.

I would like to add a method called #send_email? that all member subclasses respond to. By default this method will return false, but will be overridden for high priority alarms to return true.

classPriority < ClassyEnum::Basedefsend_email?falseendendclassPriority::Low < PriorityendclassPriority::Medium < PriorityendclassPriority::High < Prioritydefsend_email?trueendend

3. Setup the Active Record model

My Active Record Alarm model needs a text field that will store a string representing the enum member. An example model schema might look something like:

create_table"alarms",force: truedo |t|
t.string"priority"t.boolean"enabled"end

NOTE: Alternatively, you may use an enum type if your database supports it. See this issue for more information.

Then in my model I've included ClassyEnum::ActiveRecord and added a line that calls classy_enum_attr with a single argument representing the enum I want to associate with my model. I am also delegating the #send_email? method to my Priority enum class.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:prioritydelegate:send_email?,to: :priorityend

With this setup, I can now do the following:

@alarm=Alarm.create(priority: :medium)@alarm.priority# => Priority::Medium@alarm.priority.medium?# => true@alarm.priority.high?# => false@alarm.priority.to_s# => 'medium'# Should this alarm send an email?@alarm.send_email?# => false@alarm.priority=:high@alarm.send_email?# => true

The enum field works like any other model attribute. It can be mass-assigned using #update_attributes.

What if your enum class name is not the same as your model's attribute name?

Just provide an optional class_name argument to declare the enum's class name. In this case, the model's attribute is called alarm_priority.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:alarm_priority,class_name: 'Priority'end@alarm=Alarm.create(alarm_priority: :medium)@alarm.alarm_priority# => Priority::Medium

Internationalization

ClassyEnum provides built-in support for translations using Ruby's I18n library. The translated values are provided via a #text method on each enum object. Translations are automatically applied when a key is found at locale.classy_enum.enum_parent_class.enum_value, or a default value is used that is equivalent to #to_s.titleize.

Given the following file config/locales/es.yml

es:
classy_enum:
priority:
low: 'Bajo'medium: 'Medio'high: 'Alto'

You can now do the following:

@alarm.priority=:low@alarm.priority.text# => 'Low'I18n.locale=:es@alarm.priority.text# => 'Bajo'

Using Enum as a Collection

ClassyEnum::Base extends the Enumerable module which provides several traversal and searching methods. This can be useful for situations where you are working with the collection, as opposed to the attributes on an Active Record object.

# Find the priority based on string or symbol:Priority.find(:low)# => Priority::Low.newPriority.find('medium')# => Priority::Medium.new# Test if a priority is valid:Priority.include?(:low)# => truePriority.include?(:lower)# => false# List priorities base strings:Priority.map{ |p| p.to_s}# => ["low", "medium", "high"]# Find the lowest priority that can send email:Priority.find(&:send_email?)# => Priority::High.new# Find the priorities that are lower than Priority::HighPriority.select{|p| p < :high}# => [Priority::Low.new, Priority::Medium.new]# Iterate over each priority:Priority.eachdo |priority|
putspriority.send_email?end

Default Enum Value

As with any Active Record attribute, default values can be specified in the database table and will propagate to new instances. However, there may be times when you can't or don't want to set the default value in the database. For these occasions, a default value can be specified like so:

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,default: 'medium'endAlarm.new.priority# => Priority::Medium

You may also use a Proc object to set the default value. The enum class is yielded to the block and can be used to determine the default at runtime.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,default: ->(enum){enum.max}endAlarm.new.priority# => Priority::High

Back Reference to Owning Object

In some cases you may want an enum class to reference the owning object (an instance of the Active Record model). Think of it as a belongs_to relationship, where the enum belongs to the model.

By default, the back reference can be called using #owner. If you want to refer to the owner by a different name, you must explicitly declare the owner name in the classy_enum parent class using the .owner class method.

Example using the default #owner method:

classPriority < ClassyEnum::Baseend# low and medium subclasses omittedclassPriority::High < Prioritydefsend_email?owner.enabled?endend

Example where the owner reference is explicitly declared:

classPriority < ClassyEnum::Baseowner:alarmend# low and medium subclasses omittedclassPriority::High < Prioritydefsend_email?alarm.enabled?endend

In the above examples, high priority alarms are only emailed if the owning alarm is enabled.

@alarm=Alarm.create(priority: :high,enabled: true)# Should this alarm send an email?@alarm.send_email?# => true@alarm.enabled=false@alarm.send_email?# => false

Model Validation

An Active Record validator validates_inclusion_of :field, in: ENUM is automatically added to your model when you use classy_enum_attr.

If your enum only has members low, medium, and high, then the following validation behavior would be expected:

@alarm=Alarm.new(priority: :really_high)@alarm.valid?# => false@alarm.priority=:high@alarm.valid?# => true

To allow nil or blank values, you can pass in :allow_nil and :allow_blank as options to classy_enum_attr:

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,allow_nil: trueend@alarm=Alarm.new(priority: nil)@alarm.valid?# => true

Active Record Querying

Classy Enum classes are plain Ruby objects, and Active Record does not know how to typecast them to strings when querying. Therefore you must explicitly convert the object to a string or symbol for the query to be valid.

Classy Enum versions before 4.0.0 supported querying directly with the enum objects. Suppport for this was removed in 4.0.0 because it depended on ARel internals, and maintaining backwards compatibility with older version of Rails was not possible.

Any of these are valid:

Alarm.where(priority: 'high')Alarm.where(priority: Priority[:high].to_s)Alarm.where(priority: Priority::High.new.to_s)

Note If you get an error like Cannot visit <Enum>, it means that ActiveRecord - or more accurately ARel - has received the Classy Enum class or instance directly, rather than a string representing corresponding to the database value. To resolve this issue, you need to convert the object to a string.

Form Usage

ClassyEnum includes a select_options helper method to generate an array of enum options that can be used by Rails' form builders such as SimpleForm and Formtastic.

# SimpleForm
<%= simple_form_for @alarm do |f| %><%= f.input :priority, as: :select, collection: Priority.select_options %><%= f.button :submit %><% end %>

Testing Enums

ClassyEnums can be tested by creating new instances of the ClassyEnum and testing expectations. For example:

classTestPriorityHigh < Minitest::Testdefsetup@priority_high_enum=Priority::High.newenddeftest_send_email_enabledassert@priority_high_enum.send_email?endend

If the ClassyEnum method implementations rely upon the owner, the ClassyEnum#build method can be used with the owner option. For example:

classTestPriorityHigh < Minitest::Testdefsetup@alarm=Alarm.create@priority_high_enum=Priority::High.build(:high,owner: @alarm)enddeftest_send_email_enabledassert_equal@priority_high_enum.owner,@alarmendend

Copyright

Copyright (c) 2010-2016 Peter Brown. See LICENSE for details.

About

A class-based enumerator gem for Rails

Resources

Contributing

Stars

273 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

ClassyEnum

Build StatusGem VersionCode ClimateDependency Status

ClassyEnum is a Ruby on Rails gem that adds class-based enumerator functionality to Active Record attributes.

This README is also available in a user-friendly DocumentUp format.

Rails & Ruby Versions Supported

Rails: 3.2.x - 4.2.x

Ruby: 1.9.3, 2.0.0, 2.1.x, 2.2.x, and 2.3.x

Installation

The gem is hosted at rubygems.org

Despite RailsGuides claiming that all directories under app will be autoloaded, I've had reports of this not being the case with newer versions of Ruby and Rails.

You may need to add the enums path to config/application.rb:

# Make sure classy_enum enums get loadedconfig.autoload_paths += %W(#{config.root}/app/enums)

Upgrading?

See the wiki for notes about upgrading from previous versions.

Getting Started & Example Usage

The most common use for ClassyEnum is to replace database lookup tables where the content and behavior is mostly static and has multiple "types". Please see the Wiki for a short discussion on use cases comparing ClassyEnum to other gems.

In this example, I have an Active Record model called Alarm with an attribute called priority. Priority is stored as a string (VARCHAR) type in the database and is converted to an enum value when requested.

1. Generate the Enum

The fastest way to get up and running with ClassyEnum is to use the built-in Rails generator like so:

rails generate classy_enum Priority low medium high

NOTE: You may destroy/revoke an enum by using the rails destroy command:

rails destroy classy_enum Priority

A new enum template file will be created at app/enums/priority.rb that will look like:

classPriority < ClassyEnum::BaseendclassPriority::Low < PriorityendclassPriority::Medium < PriorityendclassPriority::High < Priorityend

NOTE: The class order is important because it defines the enum member ordering as well as additional ClassyEnum behavior described below.

2. Customize the Enum

The generator creates a default setup, but each enum member can be changed to fit your needs.

I have defined three priority levels: low, medium, and high. Each priority level can have different properties and methods associated with it.

I would like to add a method called #send_email? that all member subclasses respond to. By default this method will return false, but will be overridden for high priority alarms to return true.

classPriority < ClassyEnum::Basedefsend_email?falseendendclassPriority::Low < PriorityendclassPriority::Medium < PriorityendclassPriority::High < Prioritydefsend_email?trueendend

3. Setup the Active Record model

My Active Record Alarm model needs a text field that will store a string representing the enum member. An example model schema might look something like:

create_table"alarms",force: truedo |t|
t.string"priority"t.boolean"enabled"end

NOTE: Alternatively, you may use an enum type if your database supports it. See this issue for more information.

Then in my model I've included ClassyEnum::ActiveRecord and added a line that calls classy_enum_attr with a single argument representing the enum I want to associate with my model. I am also delegating the #send_email? method to my Priority enum class.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:prioritydelegate:send_email?,to: :priorityend

With this setup, I can now do the following:

@alarm=Alarm.create(priority: :medium)@alarm.priority# => Priority::Medium@alarm.priority.medium?# => true@alarm.priority.high?# => false@alarm.priority.to_s# => 'medium'# Should this alarm send an email?@alarm.send_email?# => false@alarm.priority=:high@alarm.send_email?# => true

The enum field works like any other model attribute. It can be mass-assigned using #update_attributes.

What if your enum class name is not the same as your model's attribute name?

Just provide an optional class_name argument to declare the enum's class name. In this case, the model's attribute is called alarm_priority.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:alarm_priority,class_name: 'Priority'end@alarm=Alarm.create(alarm_priority: :medium)@alarm.alarm_priority# => Priority::Medium

Internationalization

ClassyEnum provides built-in support for translations using Ruby's I18n library. The translated values are provided via a #text method on each enum object. Translations are automatically applied when a key is found at locale.classy_enum.enum_parent_class.enum_value, or a default value is used that is equivalent to #to_s.titleize.

Given the following file config/locales/es.yml

es:
classy_enum:
priority:
low: 'Bajo'medium: 'Medio'high: 'Alto'

You can now do the following:

@alarm.priority=:low@alarm.priority.text# => 'Low'I18n.locale=:es@alarm.priority.text# => 'Bajo'

Using Enum as a Collection

ClassyEnum::Base extends the Enumerable module which provides several traversal and searching methods. This can be useful for situations where you are working with the collection, as opposed to the attributes on an Active Record object.

# Find the priority based on string or symbol:Priority.find(:low)# => Priority::Low.newPriority.find('medium')# => Priority::Medium.new# Test if a priority is valid:Priority.include?(:low)# => truePriority.include?(:lower)# => false# List priorities base strings:Priority.map{ |p| p.to_s}# => ["low", "medium", "high"]# Find the lowest priority that can send email:Priority.find(&:send_email?)# => Priority::High.new# Find the priorities that are lower than Priority::HighPriority.select{|p| p < :high}# => [Priority::Low.new, Priority::Medium.new]# Iterate over each priority:Priority.eachdo |priority|
putspriority.send_email?end

Default Enum Value

As with any Active Record attribute, default values can be specified in the database table and will propagate to new instances. However, there may be times when you can't or don't want to set the default value in the database. For these occasions, a default value can be specified like so:

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,default: 'medium'endAlarm.new.priority# => Priority::Medium

You may also use a Proc object to set the default value. The enum class is yielded to the block and can be used to determine the default at runtime.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,default: ->(enum){enum.max}endAlarm.new.priority# => Priority::High

Back Reference to Owning Object

In some cases you may want an enum class to reference the owning object (an instance of the Active Record model). Think of it as a belongs_to relationship, where the enum belongs to the model.

By default, the back reference can be called using #owner. If you want to refer to the owner by a different name, you must explicitly declare the owner name in the classy_enum parent class using the .owner class method.

Example using the default #owner method:

classPriority < ClassyEnum::Baseend# low and medium subclasses omittedclassPriority::High < Prioritydefsend_email?owner.enabled?endend

Example where the owner reference is explicitly declared:

classPriority < ClassyEnum::Baseowner:alarmend# low and medium subclasses omittedclassPriority::High < Prioritydefsend_email?alarm.enabled?endend

In the above examples, high priority alarms are only emailed if the owning alarm is enabled.

@alarm=Alarm.create(priority: :high,enabled: true)# Should this alarm send an email?@alarm.send_email?# => true@alarm.enabled=false@alarm.send_email?# => false

Model Validation

An Active Record validator validates_inclusion_of :field, in: ENUM is automatically added to your model when you use classy_enum_attr.

If your enum only has members low, medium, and high, then the following validation behavior would be expected:

@alarm=Alarm.new(priority: :really_high)@alarm.valid?# => false@alarm.priority=:high@alarm.valid?# => true

To allow nil or blank values, you can pass in :allow_nil and :allow_blank as options to classy_enum_attr:

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,allow_nil: trueend@alarm=Alarm.new(priority: nil)@alarm.valid?# => true

Active Record Querying

Classy Enum classes are plain Ruby objects, and Active Record does not know how to typecast them to strings when querying. Therefore you must explicitly convert the object to a string or symbol for the query to be valid.

Classy Enum versions before 4.0.0 supported querying directly with the enum objects. Suppport for this was removed in 4.0.0 because it depended on ARel internals, and maintaining backwards compatibility with older version of Rails was not possible.

Any of these are valid:

Alarm.where(priority: 'high')Alarm.where(priority: Priority[:high].to_s)Alarm.where(priority: Priority::High.new.to_s)

Note If you get an error like Cannot visit <Enum>, it means that ActiveRecord - or more accurately ARel - has received the Classy Enum class or instance directly, rather than a string representing corresponding to the database value. To resolve this issue, you need to convert the object to a string.

Form Usage

ClassyEnum includes a select_options helper method to generate an array of enum options that can be used by Rails' form builders such as SimpleForm and Formtastic.

# SimpleForm
<%= simple_form_for @alarm do |f| %><%= f.input :priority, as: :select, collection: Priority.select_options %><%= f.button :submit %><% end %>

Testing Enums

ClassyEnums can be tested by creating new instances of the ClassyEnum and testing expectations. For example:

classTestPriorityHigh < Minitest::Testdefsetup@priority_high_enum=Priority::High.newenddeftest_send_email_enabledassert@priority_high_enum.send_email?endend

If the ClassyEnum method implementations rely upon the owner, the ClassyEnum#build method can be used with the owner option. For example:

classTestPriorityHigh < Minitest::Testdefsetup@alarm=Alarm.create@priority_high_enum=Priority::High.build(:high,owner: @alarm)enddeftest_send_email_enabledassert_equal@priority_high_enum.owner,@alarmendend

Copyright

Copyright (c) 2010-2016 Peter Brown. See LICENSE for details.

About

A class-based enumerator gem for Rails

Resources

Contributing

Stars

273 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

ClassyEnum

Build StatusGem VersionCode ClimateDependency Status

ClassyEnum is a Ruby on Rails gem that adds class-based enumerator functionality to Active Record attributes.

This README is also available in a user-friendly DocumentUp format.

Rails & Ruby Versions Supported

Rails: 3.2.x - 4.2.x

Ruby: 1.9.3, 2.0.0, 2.1.x, 2.2.x, and 2.3.x

Installation

The gem is hosted at rubygems.org

Despite RailsGuides claiming that all directories under app will be autoloaded, I've had reports of this not being the case with newer versions of Ruby and Rails.

You may need to add the enums path to config/application.rb:

# Make sure classy_enum enums get loadedconfig.autoload_paths += %W(#{config.root}/app/enums)

Upgrading?

See the wiki for notes about upgrading from previous versions.

Getting Started & Example Usage

The most common use for ClassyEnum is to replace database lookup tables where the content and behavior is mostly static and has multiple "types". Please see the Wiki for a short discussion on use cases comparing ClassyEnum to other gems.

In this example, I have an Active Record model called Alarm with an attribute called priority. Priority is stored as a string (VARCHAR) type in the database and is converted to an enum value when requested.

1. Generate the Enum

The fastest way to get up and running with ClassyEnum is to use the built-in Rails generator like so:

rails generate classy_enum Priority low medium high

NOTE: You may destroy/revoke an enum by using the rails destroy command:

rails destroy classy_enum Priority

A new enum template file will be created at app/enums/priority.rb that will look like:

classPriority < ClassyEnum::BaseendclassPriority::Low < PriorityendclassPriority::Medium < PriorityendclassPriority::High < Priorityend

NOTE: The class order is important because it defines the enum member ordering as well as additional ClassyEnum behavior described below.

2. Customize the Enum

The generator creates a default setup, but each enum member can be changed to fit your needs.

I have defined three priority levels: low, medium, and high. Each priority level can have different properties and methods associated with it.

I would like to add a method called #send_email? that all member subclasses respond to. By default this method will return false, but will be overridden for high priority alarms to return true.

classPriority < ClassyEnum::Basedefsend_email?falseendendclassPriority::Low < PriorityendclassPriority::Medium < PriorityendclassPriority::High < Prioritydefsend_email?trueendend

3. Setup the Active Record model

My Active Record Alarm model needs a text field that will store a string representing the enum member. An example model schema might look something like:

create_table"alarms",force: truedo |t|
t.string"priority"t.boolean"enabled"end

NOTE: Alternatively, you may use an enum type if your database supports it. See this issue for more information.

Then in my model I've included ClassyEnum::ActiveRecord and added a line that calls classy_enum_attr with a single argument representing the enum I want to associate with my model. I am also delegating the #send_email? method to my Priority enum class.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:prioritydelegate:send_email?,to: :priorityend

With this setup, I can now do the following:

@alarm=Alarm.create(priority: :medium)@alarm.priority# => Priority::Medium@alarm.priority.medium?# => true@alarm.priority.high?# => false@alarm.priority.to_s# => 'medium'# Should this alarm send an email?@alarm.send_email?# => false@alarm.priority=:high@alarm.send_email?# => true

The enum field works like any other model attribute. It can be mass-assigned using #update_attributes.

What if your enum class name is not the same as your model's attribute name?

Just provide an optional class_name argument to declare the enum's class name. In this case, the model's attribute is called alarm_priority.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:alarm_priority,class_name: 'Priority'end@alarm=Alarm.create(alarm_priority: :medium)@alarm.alarm_priority# => Priority::Medium

Internationalization

ClassyEnum provides built-in support for translations using Ruby's I18n library. The translated values are provided via a #text method on each enum object. Translations are automatically applied when a key is found at locale.classy_enum.enum_parent_class.enum_value, or a default value is used that is equivalent to #to_s.titleize.

Given the following file config/locales/es.yml

es:
classy_enum:
priority:
low: 'Bajo'medium: 'Medio'high: 'Alto'

You can now do the following:

@alarm.priority=:low@alarm.priority.text# => 'Low'I18n.locale=:es@alarm.priority.text# => 'Bajo'

Using Enum as a Collection

ClassyEnum::Base extends the Enumerable module which provides several traversal and searching methods. This can be useful for situations where you are working with the collection, as opposed to the attributes on an Active Record object.

# Find the priority based on string or symbol:Priority.find(:low)# => Priority::Low.newPriority.find('medium')# => Priority::Medium.new# Test if a priority is valid:Priority.include?(:low)# => truePriority.include?(:lower)# => false# List priorities base strings:Priority.map{ |p| p.to_s}# => ["low", "medium", "high"]# Find the lowest priority that can send email:Priority.find(&:send_email?)# => Priority::High.new# Find the priorities that are lower than Priority::HighPriority.select{|p| p < :high}# => [Priority::Low.new, Priority::Medium.new]# Iterate over each priority:Priority.eachdo |priority|
putspriority.send_email?end

Default Enum Value

As with any Active Record attribute, default values can be specified in the database table and will propagate to new instances. However, there may be times when you can't or don't want to set the default value in the database. For these occasions, a default value can be specified like so:

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,default: 'medium'endAlarm.new.priority# => Priority::Medium

You may also use a Proc object to set the default value. The enum class is yielded to the block and can be used to determine the default at runtime.

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,default: ->(enum){enum.max}endAlarm.new.priority# => Priority::High

Back Reference to Owning Object

In some cases you may want an enum class to reference the owning object (an instance of the Active Record model). Think of it as a belongs_to relationship, where the enum belongs to the model.

By default, the back reference can be called using #owner. If you want to refer to the owner by a different name, you must explicitly declare the owner name in the classy_enum parent class using the .owner class method.

Example using the default #owner method:

classPriority < ClassyEnum::Baseend# low and medium subclasses omittedclassPriority::High < Prioritydefsend_email?owner.enabled?endend

Example where the owner reference is explicitly declared:

classPriority < ClassyEnum::Baseowner:alarmend# low and medium subclasses omittedclassPriority::High < Prioritydefsend_email?alarm.enabled?endend

In the above examples, high priority alarms are only emailed if the owning alarm is enabled.

@alarm=Alarm.create(priority: :high,enabled: true)# Should this alarm send an email?@alarm.send_email?# => true@alarm.enabled=false@alarm.send_email?# => false

Model Validation

An Active Record validator validates_inclusion_of :field, in: ENUM is automatically added to your model when you use classy_enum_attr.

If your enum only has members low, medium, and high, then the following validation behavior would be expected:

@alarm=Alarm.new(priority: :really_high)@alarm.valid?# => false@alarm.priority=:high@alarm.valid?# => true

To allow nil or blank values, you can pass in :allow_nil and :allow_blank as options to classy_enum_attr:

classAlarm < ActiveRecord::BaseincludeClassyEnum::ActiveRecordclassy_enum_attr:priority,allow_nil: trueend@alarm=Alarm.new(priority: nil)@alarm.valid?# => true

Active Record Querying

Classy Enum classes are plain Ruby objects, and Active Record does not know how to typecast them to strings when querying. Therefore you must explicitly convert the object to a string or symbol for the query to be valid.

Classy Enum versions before 4.0.0 supported querying directly with the enum objects. Suppport for this was removed in 4.0.0 because it depended on ARel internals, and maintaining backwards compatibility with older version of Rails was not possible.

Any of these are valid:

Alarm.where(priority: 'high')Alarm.where(priority: Priority[:high].to_s)Alarm.where(priority: Priority::High.new.to_s)

Note If you get an error like Cannot visit <Enum>, it means that ActiveRecord - or more accurately ARel - has received the Classy Enum class or instance directly, rather than a string representing corresponding to the database value. To resolve this issue, you need to convert the object to a string.

Form Usage

ClassyEnum includes a select_options helper method to generate an array of enum options that can be used by Rails' form builders such as SimpleForm and Formtastic.

# SimpleForm
<%= simple_form_for @alarm do |f| %><%= f.input :priority, as: :select, collection: Priority.select_options %><%= f.button :submit %><% end %>

Testing Enums

ClassyEnums can be tested by creating new instances of the ClassyEnum and testing expectations. For example:

classTestPriorityHigh < Minitest::Testdefsetup@priority_high_enum=Priority::High.newenddeftest_send_email_enabledassert@priority_high_enum.send_email?endend

If the ClassyEnum method implementations rely upon the owner, the ClassyEnum#build method can be used with the owner option. For example:

classTestPriorityHigh < Minitest::Testdefsetup@alarm=Alarm.create@priority_high_enum=Priority::High.build(:high,owner: @alarm)enddeftest_send_email_enabledassert_equal@priority_high_enum.owner,@alarmendend

Copyright

Copyright (c) 2010-2016 Peter Brown. See LICENSE for details.

About

A class-based enumerator gem for Rails

Resources

Contributing

Stars

273 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages