ConstantizeAttribute to support Renum on Rails
The most obvious shortcoming of Renum is that there hasn't been any clean way to use enumerated values as ActiveRecord attribute values. I've finally fixed that.
There were a couple of Rails features that seemed like they might be helpful but turned out not to be.
Rails' built-in serialize macro class method uses YAML to store items, which is fine for arrays and hashes but hideous for anything else (if you ever look at your database directly). Though YAML serialization worked on Renum-created enumerated values, deserializing a value created a new instance. That instance might turn out to work fine for your needs, but it wouldn't actually be the instance it ought to be (i.e., the one the constant points to), so it might surprise and disappoint you in subtle ways. The other huge downside to serializing the instance (and its instance variables) is that a change in the encapsulated contents of the enumerated value could break things.
Rails' built-in composed_of macro class method could have been made to work, but it wouldn't have been pretty. The default handling would have required Renum to redefine new
in generated enumeration classes to do a lookup instead of allocating and initializing an instance. The latest releases of Rails provide :constructor
and :converter
options that would have allowed me to avoid messing with new
, but it still would've been ugly, not to mention requiring a very recent version of Rails.
What I finally realized is that the beauty of constants is that I didn't need to write a lookup mechanism: Ruby already does that. All I needed to do was store the name of the constant when writing the attribute and constantize it when reading to get the proper value. I also realized that approach is in no way tied to Renum: It would also allow classes and modules to be attribute values, which could be helpful if, for example, you have a module to mix in or a service class to call based on some reference data.
So I wrote a tiny little Rails plugin called ConstantizeAttribute that does this for you. This example pretty much says it all:
# ... your migration ...
create_table :robots do |t|
t.column :behavior_module, :string
end
# ... your model ...
end
# ... some classes or modules you want to store as attribute values ...
"Is there anything I can help you with?"
end
end
"I will destroy all humans."
end
end
end
# ... so now,
robby = Robot.create :behavior_module => RobotBehaviors::Evil
# Now "RobotBehaviors::Evil" is in the behavior_module column.
robby.behavior_module.encounter # => "I will destroy all humans."
robby.update_attribute :behavior_module, RobotBehaviors::Handy
# Now "RobotBehaviors::Handy" is in the behavior_module column.
robby = Robot.find :first
robby.behavior_module.encounter # => "Is there anything I can help you with?"
Install ConstantizeAttribute with
script/plugin install git://github.com/duelinmarkers/constantize_attribute.git
if your Rails is recent enough to install from git or grab a copy of the repo manually and drop the plugin in place. (There's no install script to worry about.)
The cool thing about this is that it works with old version of Renum without any changes, and it all happens in only about 15 lines of code.
No comments:
Post a Comment