Fun with Rails Constantize
Have you ever found yourself working on a Rails project where you need certain behavior in a model based on type but STI[1] is not the right fit or you are already using STI and can not subtype again? I was working on a problem where a model utilized STI to create two types: Icon and Color. Within each type I had different data. Some of the data represented defaults for Icons or defaults for Colors and the others were specific. I’ve put together a little diagram on the right to illustrate this[2].
The way we had modeled the data made it very easy to pick out the specific icon or color theme (using an ActiveRecord finder for that type). However, based on the data instance returned by find I wanted to execute a series of methods to populate other models with data relevant to the Icon or Color instance.
A couple solutions presented themselves[3]:
- Refactor the model so that each type was represented by a model type (the orange boxes would become brown ones). We could then call the method we needed from that type.
- Each data type in our environment would only have one record. Changing the model this way didn’t really make any sense in our situation.
- Create a map from data element name to a class that could be invoked to perform the required actions.
- This would work, but I was feeling lazy and maintaining a map of names to types didn’t feel as DRY as I thought it could be.
- Take advantage of the Rails constantize method.
- We could use the name of the record and dynamically map that string to a class with a method or methods that could be invoked at runtime.
While any of theses solutions could work, the one that I found the most interesting was the one involving constantize (Module
ActiveSupport::CoreExtensions::String::Inflections). If you have poked around the rails source you see that constantize is used for things like in_place_edit (do a find on an object based on a parameter string). If I haven’t lost you yet included below is a code snippet illustrating how this works and solving my problem. It’s important to note that you need to camelize specific_theme_type before calling constantize or it will raise a NameException. In the example below I’ve created a rescue block because not all of my types are mapped to a specific content class and I wanted to fall back to a default in those cases.
begin content_type = "Content::#{specific_theme_type.camelize}Content".constantize rescue NameError content_type = Content::Default end content_type.content_method
I’ve only touched the surface of things you can do with constantize. If you have also used constantize or another similar trick, please share it in the comments.
Notes:
- Single Table Inheritance – Definition of STI, STI in Rails
- Apologies for the small digram, full size available here.
- I am sure there are other solutions, so feel free to leave comments detailing how you would have done it
January 8, 2008 at 3:06 pm
Yup, Constantize rocks!
Also don’t know how I’d live without hacks like Model.send (”foo_#{bar}”) or eval(”model.foo_#{bar}=true”).
February 17, 2008 at 9:59 pm
Model.send (”foo_#{bar}=”,true) is perhaps a better option?
December 15, 2008 at 8:50 am
Nice example.
However, as of rails v2.1.0, constantize has been deprecated.
http://apidock.com/rails/Inflector/constantize