- We design and build extraordinary applications for companies looking to make the next great idea a reality.
- learn more
Using ActiveRecord and Metaprogramming to Define Constants for Enumerated Types
Have you ever stored your application's enumerated types in a database? If you have, you might have also noticed that code will often times "duplicate" this data by defining constants or enums that reference what is in the database. If you're anything like me, this duplication does not feel right.
With the metaprogramming capabilities of Ruby, we can address the maintenance costs of duplication by generating these constants at runtime.
Take for example the following data, which represents available fruits in our system:
id name 1 orange 2 apple 3 banana
In our system, we want to be able to refer to these fruit types in the code as FruitType::ORANGE, etc. Let's start with a unit test to document the behavior which we want.
class FruitTypeTest < ActiveSupport::TestCase
def test_constants_defined
assert_equal FruitType.find_by_name('orange'), FruitType::ORANGE
assert_equal FruitType.find_by_name('apple'), FruitType::APPLE
assert_equal FruitType.find_by_name('banana'), FruitType::BANANA
end
end
This test fails with undefined constant, which is exactly what we'd expect. Now that we have a failing unit test, we can write some code to make the test pass.
class FruitType < ActiveRecord::Base
find(:all).each do |type|
eval "#{type.name.upcase} = type"
end
end
With only those couple lines of code, the test passes! But how exactly does this code work?
First, we leverage active record to find all the fruit types defined in the database. This is pretty basic active record stuff, and isn't that interesting.
The next bit is a little more interesting. We enumerate over each result, and use metaprogramming to define a constant in the class for every fruit type found in the database. This little bit of code relies on the eval method, which is a Ruby Kernel method that evaluates the passed string as an expression. Since no binding is passed to eval, it evaluates in the current context (which happens to be the FruitType class). The end result is the same as if you had typed ORANGE = find_by_name('orange') in the source code.
While this example is pretty straightforward and simple, much more can be accomplished through this approach. However, discretion should be used when employing this tactic. Just because you can change the behavior of Ruby core classes based on some data you have in your database doesn't mean you should...
Comments: 1 so far
Leave a comment
About Pathfinder
Recent
- Rails ThreatDown!
- Automated Deployments Rock
- Bandwidth profiling Flex projects and more with Charles
- iPhone SDK: UIViewController Testing & TDD
- Icons are evil; so are menus - unless you do them right
- The Truth About Designing For Security
- GWT, Gadgets and OpenSocial, Part 2
- Has Many has_many: A Refactoring Story
- The Hidden Power of Canvas
- Review of fixture_replacement2 plugin
Archives
- November 2008
- October 2008
- September 2008
- August 2008
- July 2008
- June 2008
- May 2008
- April 2008
- March 2008
- February 2008
- January 2008
- December 2007
- November 2007
- October 2007
- September 2007
- August 2007
- July 2007
- June 2007
- May 2007
- April 2007
- March 2007
- February 2007
- January 2007
- December 2006
- November 2006
- October 2006
- September 2006
- August 2006
- July 2006
- June 2006
- May 2006
- April 2006
- March 2006



very clever
Comment by Oscar Del Ben, Tuesday, May 27, 2008 @ 4:45 pm