FlexibleEnum
Give Ruby enum-like powers.
Installation
Add this line to your application's Gemfile:
gem "flexible_enum"
And then execute:
$ bundle
Or install it yourself as:
$ gem install flexible_enum
Basic Usage
The flexible_enum
class method is mixed into ActiveRecord::Base. Call it to add enum-like powers to any number of existing attributes on a target class.
You must provide the name of the attribute and a list of available options. Options consist of a name, value, and optional hash of configuration parameters.
class User < ActiveRecord::Base
flexible_enum :status do
active 0
disabled 1
pending 2
end
end
Option values may be any type.
class Product < ActiveRecord::Base
flexible_enum :manufacturer do
honeywell "HON"
sharp "SHCAY"
end
end
Working with Values
Available options for each attribute are defined as constants on the target class. The classes above would have defined:
User::ACTIVE # => 0
User::DISABLED # => 1
User::PENDING # => 2
Product::HONEYWELL # => "HON"
Product::SHARP # => "SHCAY"
Setter Methods
FlexibleEnum adds convenience methods for changing the current value of an attribute and immediately saving it to the database. By default, bang methods are added for each option:
u = User.new
u.active! # Calls update_attributes(status: 0)
u.disabled! # Calls update_attributes(status: 1)
The name of the setter method can be changed using option configuration parameters:
class Post < ActiveRecord::Base
flexible_enum :visibility do
invisible 0, setter: :hide!
visible 1, setter: :show!
end
end
p = Post.new
p.show! # Calls update_attributes(visibility: 1)
p.hide! # Calls update_attributes(visibility: 0)
Timestamps
If the target class defines a date and/or time attribute corresponding to the flexible enum option being set it will be updated with the current date/time when using setter methods. For example, Post#show! above will set visibility = 1
, visibile_at = Time.now.utc
, and visible_on = Time.now.utc.to_date
if those columns exist. The existance of columns is checked using ActiveRecord's attribute_method?
method.
Use the :timestamp_attribute
option configuration parameter to change the columns used:
flexible_enum :status do
unknown 0
active 1, timestamp_attribute: :actived
disabled 2, timestamp_attribute: :disabled
end
Calling active!
will now attempt to set actived_at
and actived_on
.
Predicate Methods
FlexibleEnum adds convenience methods for checking whether an option's value is also the attribute's current value.
p = Post.new
p.show!
p.visible? # => true
p.invisible? # => false
Inverse predicate methods can be added by setting the :inverse configuration parameter. Inverse predicate methods have the reverse logic:
class Car < ActiveRecord::Base
flexible_enum :fuel_type do
gasoline 0
diesel 1
electric 2, inverse: :carbon_emitter
end
end
c = Car.new
c.gasoline!
c.carbon_emitter? # => true
c.diesel!
c.carbon_emitter? # => true
c.electric!
c.carbon_emitter? # => false
Humanized Values
Humanized versions of attributes are available. This is convenient for displaying the current value on screen (see "Option Reflection" for rendering drop down lists).
c = Car.new(fuel_type: Car::DIESEL)
c.human_fuel_type = "Diesel"
Car.human_fuel_type(0) # => "Gasoline"
Car.fuel_types.collect(&:human_name) # => ["Gasoline", "Diesel", "Electric"]
If the flexible enum value is nil
, the humanized name will also be nil
:
c = Car.new(fuel_type: nil)
c.human_fuel_type # => nil
Name Method
The name of the attribute value is available. This allows you to grab the stringified version of the name of the value.
c = Car.new(fuel_type: Car::CARBON_EMITTER)
c.fuel_type_name # => "carbon_emitter"
If the flexible enum value is nil
, the name will also be nil
:
c = Car.new(fuel_type: nil)
c.fuel_type_name # => nil
Namespaced Attributes
FlexibleEnum attributes may be namespaced. Adding the namespace option to flexible_enum
results in constants being defined in a new module.
class CashRegister < ActiveRecord::Base
flexible_enum :drawer_position, namespace: "DrawerPositions" do
opened 0
closed 1
end
end
# Constants are defined in a new module
CashRegister::DrawerPositions::OPENED # => 0
CashRegister::DrawerPositions::CLOSED # => 1
# Convenience methods are not affected by namespace
r = CashRegister.new
r.opened!
r.closed!
Scopes
FlexibleEnum adds ActiveRecord scopes for each attribute option:
User.active # => User.where(status: 0)
User.disabled # => User.where(status: 1)
User.pending # => User.where(status: 2)
When an attribute is namespaced a prefix is added to scope names. The prefix is the singularized namespace name (using Active Support):
CashRegister.drawer_position_opened # => CashRegister.where(drawer_position: 0)
CashRegister.drawer_position_closed # => CashRegister.where(drawer_position: 1)
Inverse scopes can be added by setting the :inverse configuration parameter:
class Car < ActiveRecord::Base
flexible_enum :fuel_type do
gasoline 0
diesel 1
electric 2, inverse: :carbon_emitter
end
end
gas = Car.create(fuel_type: Car::GASOLINE)
diesel = Car.create(fuel_type: Car::DIESEL)
electric = Car.create(fuel_type: Car::ELECTRIC)
Car.carbon_emitter # => [gasoline, diesel]
Note about default scopes
Be careful when using default scopes on FlexibleEnum columns. Since FlexibleEnum provides scopes for enum values, setting a default_scope
on a FlexibleEnum column will result in conflicts. For example, given this model:
class User < ActiveRecord::Base
flexible_enum :status do
active 1
inactive 2
end
default_scope -> { where(status: ACTIVE) }
end
Attempts to use the User.inactive
scope that FlexibleEnum provides will result in this SQL:
SELECT * FROM users WHERE users.status = 1 AND users.status = 2
You will need to unscope
the default_scope
before using a FlexibleEnum-provided scope (as you would have to do for normal Rails scopes that happen to contradict each other).
User.unscope(where: :status).inactive
Custom Options
Configuration parameters passed to attribute options are saved even if they are unknown.
class EmailEvent < ActiveRecord::Base
flexible_enum :event_type do
bounce 1, processor_class: RejectedProcessor
dropped 2, processor_class: RejectedProcessor
opened 3, processor_class: EmailOpenedProcessor
delivered 4, processor_class: DeliveryProcessor
end
end
Custom configuration parameters are available as an instance method on the object as well.
e = EmailEvent.new(event_type: 1)
e.event_type_details # => { processor_class: RejectedProcessor, value: 1 }
Option Introspection
You may introspect on available options and their configuration parameters:
ary = EmailEvent.event_types
ary.collect(&:name) # => ["bounce", "dropped", "opened", "delivered"]
ary.collect(&:human_name) # => ["Bounce", "Dropped", "Opened", "Delivered"]
ary.collect(&:value) # => [1, 2, 3, 4]
This works particularly well with ActionView:
f.collection_select(:event_type, EmailEvent.event_types, :value, :human_name)
Enum Introspection
You may retrieve a list of all defined flexible_enum
s on a particular class:
class Car < ActiveRecord::Base
flexible_enum :status do
new 1
used 2
end
flexible_enum :car_type do
gas 1
hybrid 2
electric 3
end
end
Car.flexible_enums # => { status: Car.statuses, car_type: Car.car_types }
Overriding Methods
You may override any method defined on the target class by FlexibleEnum. In version 0.0.1, super
behaved as it would without FlexibleEnum being present, you could not call a FlexibleEnum method implementation from an overriding method. As of version 0.0.2, super
instead references the FlexibleEnum implementation of a method when overriding a FlexibleEnum-defined method.
class Item < ActiveRecord::Base
flexible_enum :availability do
discontinued 0
backorder 1
in_stock 2
end
# Version 0.0.1
# Calling super would throw NoMethodError so we'd have to reimplement the method.
def in_stock!
BackInStockNotifier.new(self).queue if backorder?
update_attribute!(status: IN_STOCK)
end
# Version 0.0.2
# Calling super works and is preferred.
def in_stock!
BackInStockNotifier.new(self).queue if backorder?
super
end
end
Contributing
Please see CONTRIBUTING.md.
Releasing
On master in a commit named
Version x.y.z
update version.rb and CHANGELOG.md with the new version.Run
rake release
to build and release to Rubygems.Run
git push origin master --tags
to push master and the new tag (created automatically in previous step) to Github.Create a Github release including the new change log entries in the description.
Thank contributors via Twitter.
About MeYou Health
FlexibleEnum is maintained by MYH, Inc.