0.0
No commit activity in last 3 years
No release in over 3 years
Helpers for enum-like fields
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

 Project Readme

Build Status Code Climate

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_enums 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, tag the commit, push the tag, and release to Rubygems.

  • Create a Github release including the new change log entries in the description.

  • Thank contributors via Twitter.

About MeYou Health

http://meyouhealth.com/

FlexibleEnum is maintained by MYH, Inc.