skip navigation

Here you will find ideas and code straight from the Software Development Team at SportsEngine. Our focus is on building great software products for the world of youth and amateur sports. We are fortunate to be able to combine our love of sports with our passion for writing code.

The SportsEngine application originated in 2006 as a single Ruby on Rails 1.2 application. Today the SportsEngine Platform is composed of more than 20 applications built on Rails and Node.js, forming a service oriented architecture that is poised to scale for the future.

About Us
Home

Better Living with Enumerated Field

09/20/2011, 9:22am CDT
By Patrick Byrne

Why we created the enumerated_field gem and how it can help you with your Rails projects.

Problems that Need Solving

Let’s say you want to store something in the database where you'll have only a few valid choices (e.g., user’s status, page layout, or player skill level). Your first thought might to be to store the value (“Novice”, “Intermediate”, “Advanced”) right in the database. What happens when you want to change “Novice” to “Beginner”? You’ll have to change your code to reflect this and change the records in the database to reflect this new name.

Maybe instead, you make a hash of the values stored in the database versus the display value: {"novice" => "Beginner", "intermediate" => "Intermediate", "advanced" => "Advanced"}. Now you can change the display value without changing the data or code. But you’ve still got some headaches ahead of you. How do you validate that records contain a valid value? How do you refer specifically to the “beginner” display string in your views?

Enter enumerated_field

We run into this pattern throughout our application, so we wrote the enumerated_field gem to solve the problem for us, letting us focus less time on wrangling values and more time making NGIN awesome. Add gem enumerated_field to your Gemfile and you’re on your way.

Say you have a Player model which stores the player's skill level. You'd set that up like so:

class Player < ActiveRecord::Base
	include EnumeratedField

	enum_field :level, [
		["Novice", "novice"],
		["Intermediate", "intermediate"],
		["Advanced", "advanced"],
	]
end

This declares that you want the three choices “Novice”, “Intermediate”, and “Advanced” to display as choices for the level attribute in your model, which will be stored in the database as “novice”, “intermediate”, and “advanced”. You can change the display values all you want, right here in the declaration, without having any effect on the underlying database values.

Look What You’ve Won

Doing this also gets you plenty of convenience methods to work with the list of valid choices for your players’ skill level.

  • Return the array with both display and database values, formatted for use with the Rails options_for_select helper, with Player.level_values or player.level_values.
  • Return a particular database value with automatically generated constants, such as Player::LEVEL_NOVICE.
  • Determine whether a player has a particular value, such as novice, with player.level_novice?.
  • Return the display value for the player’s current level with player.level_display.
  • Return all players with particular skill levels using automatically generated scopes, like Player.level_novice to return all novices.

Take a look at the gem's documentation for even more methods at your disposal.

A Sense of Validation

Out of the box, you also gain validation, by way of Rails validates_inclusion_of, to ensure that you don’t save invalid values. Feel free to disable this behavior by passing :validate => false after the list of values. If you want to keep validation, but allow blank values, pass :allow_blank => true, or to allow nil, pass :allow_nil => true.

enum_field :level, [
	["Novice", "novice"],
	["Intermediate", "intermediate"],
	["Advanced", "advanced"],
], :validate => false

enum_field :level, [
	["Novice", "novice"],
	["Intermediate", "intermediate"],
	["Advanced", "advanced"],
], :allow_nil => true

A Note on Performance

Often, people use integers for the database values, because they offer better performance than using strings. But strings provide human-readable queries. Why can’t you get amazing performance and human-readable queries? It turns out you can!

Many relational databases provide an enum type for which you supply a short list of valid values, which provide exactly this balance of performance and readability. Unfortunately, Rails doesn’t provide migrations for this type, but help is on the way. At TST Media, we use the enum_column gem to create enum columns in our database, like so:

add_column :players, :level, :enum,
	:limit => ["novice", "intermediate", "advanced"]

In Conclusion

This gem has helped us, using it nearly 30 times in NGIN so far, and we think it could help you, too. Go forth and install enumerated_field. Please also feel free to contribute on GitHub.

Tag(s): Home  Ruby