Symbolize attribute values in ActiveRecord

Posted by Andreas on Friday, September 07, 2007 at 12:09 (CEST)
Update: I put together the code below and created a rails plugin from it. It’s called activerecord_symbolize on Github.

ActiveRecord does not natively suppport column types of ENUM or SET. If you want an attribute to act like an ENUM, you’ll most probably use a string and restrict it to certain values using validates_inclusion_of. However, once you’ve got used to Ruby, you’d probably prefer to have symbols as values for these attributes. Here’s a small and easy-to-use snippet that can be used to symbolize values of any ActiveRecord attribute.

Simply drop this code snippet into your Rails application (e.g. put it into the lib directory and add a require-statement to config/environment.rb):

 1 module ActiveRecord
 2   class Base
 3     # Specifies that values of the given attributes should be returned
 4     # as symbols. The table column should be created of type string.
 5     def self.symbolize (*attr_names)
 6       attr_names.each do |attr_name|
 7         attr_name = attr_name.to_s
 8         class_eval("def #{attr_name}; read_and_symbolize_attribute('#{attr_name}'); end")
 9         class_eval("def #{attr_name}= (value); write_attribute('#{attr_name}', value.to_s); end")
10       end
11     end
12     # Return an attribute's value as a symbol
13     def read_and_symbolize_attribute (attr_name)
14       value = read_attribute(attr_name)
15       value.blank? ? nil : value.to_sym
16     end
17   end
18 end

The above code will enhance ActiveRecord::Base with a class method named symbolize. Calling this method will create a getter and a setter method for each specified attribute. The new getter will retrieve the string value of an attribute return it as a symbol (using to_sym. a blank string will become nil). The new setter converts any value to a string (using to_s) before passing it to ActiveRecord.

Now, your pseudo-enums can be symbolized and look more rubyish:

1 class User < ActiveRecord::Base
2   symbolize :status
3   validates_inclusion_of :status, :in => [ :unconfirmed, :active, :disabled ]
4 end

You can now use symbols instead of strings for attribute values almost everywhere, because attributes are usually accessed through the getter and setter methods (e.g. the above validation works fine). Plus you can still access the original value with read_attribute.

However, there are some cases, where using a symbol does not work properly, e.g. when specifying a :scope for validates_uniqueness_of. (and probably at other places, where ActiveRecord builds a SQL filter for a symbolized attribute). To build the SQL query filter, ActiveRecord gets the quoted value of a value by calling the quoted_id method. A symbol (which is an object of class Symbol) does not have this method and therefore ActiveRecord converts the value to YAML instead. This results in queries like this:

... AND status = '--- :active\n'

The solution is not hard… Simply add a quoted_id method to Symbol:

1 class Symbol
2   def quoted_id
3     # Since symbols always contain save characters (no backslash or apostrophe), it's
4     # save to skip calling ActiveRecord::ConnectionAdapters::Quoting#quote_string here
5     "'#{self.to_s}'"
6   end
7 end

ActiveRecord now quotes symbolic values correctly:

... AND status = 'active'

Actually, I’d prefer to add the quoted_id method only to symbols that are returned by read_and_symbolize_attribute, but unfortunately, Symbol is an immediate value and therefore, you cannot add a singleton method to symbols. However I didn’t encounter any side effects yet by applying quoted_id globally to Symbol.

The above code is just a small hack I found to be useful in my applications. If you need more enumeration features than just symbolized attribute values, there’s e.g. an enum-column plugin for rails (however, I didn’t check it out and I don’t know if it works with edge rails).

10 comments

An article was published 3 days later
[...] (more…) [...]
Gravatar
wijet wrote 6 months later:

Great solution for enum fields in models, well done.

Gravatar
Andreas wrote 8 months later:

Now that my Github account works, I published the rails plugin I created from this. See activerecord_symbolize on Github.

Gravatar
Stefan Rusterholz wrote 11 months later:

Thanks for this Article.

But:

  1. Since symbols always contain save characters (no backslash or apostrophe)

That’s wrong. :“’; drop little_bobby_tables”. Symbols can contain almost anything.

Regards

Stefan

Gravatar
Andreas wrote 11 months later:

You’re right indeed… I wasn’t aware about that… But anyway, SQL injection shouldn’t be a problem as long as values are properly escaped (conditions array or hash)

Gravatar
Andreas wrote 11 months later:

I updated the plugin – it always quotes symbol values now.

Gravatar
Marc wrote over 1 year later:

Thanks for a useful piece of code.

Here’s a problem in it to fix: when you use Symbolize with ruby -w, you get the following warnings.

This is worth fixing, per this article comparing such warnings to stds which spread through shared code.

$ ruby -w unit_test.rb 
/usr/local/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/validations.rb:313: warning: `*' interpreted as argument prefix
/usr/local/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/callbacks.rb:200: warning: `*' interpreted as argument prefix
/usr/local/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/dirty.rb:40: warning: `*' interpreted as argument prefix
Loaded suite unit_test
Started
...........
Finished in 0.059982 seconds.

11 tests, 87 assertions, 0 failures, 0 errors

Gravatar
Marc wrote over 1 year later:

Whoa! Sorry about the repeat posting. Fix your Ajax-ified Send button apparatus!

Gravatar
Andreas wrote over 1 year later:

Marc, these warnings seem to point to code in ActiveRecord itself. I get the same warnings when running without the symbolize code (just a plain require 'active_record' is enough to trigger these warnings). Unfortunately, the site hosting the article you refer to, seems to be unreachable right now, so I don’t know why you think it’s worth fixing. But you might want to contact the ActiveRecord maintainers if you think it’s important (I guess the Rails bug tracker at lighthouse would be a good place to report it).

Something’s wrong with the comment submit button, I know… From time to time, it happens that people post comments multiple times — I’m not sure why this happens and I can’t reproduce it yet.

Gravatar
Justin Smestad wrote over 1 year later:

I implemented this as a gem for use with Merb / Rails3. Its available on github: http://github.com/jsmestad/merb_activerecord_enum/tree

Comments are closed