I recently found myself facing a metaprogramming challenge. I solved it by combining two terrible ugly hacks, and as such I won’t say I found a solution that is anywhere near to be elegant.
My problem was this: I was developing a Radiant extension called tags_multi_site, which allows the tags extension to play nice with the multi_site extension. This required me to scope all tags within a site, so that tags with the same name could exist in different sites, but in the same physical database table.
The tags extension has this validation:
class MetaTag < ActiveRecord::Base validates_uniqueness_of :name, :case_sensitive => false end
I needed to add :scope => :site_id, but I couldn’t touch the code of tags extension itself, since that would terribly un-DRY and not reusable for anyone else. I had to either modify the existing validation programmatically from my own extension or to remove it and add my own.
I went for the last solution. I quickly discovered that validations are saved in an array available as an inheritable attribute on the model (read_inheritable_attribute(:validate)), and that the built-in Rails validation are stored as Procs in this array. One could remove all validations added so far by emptying this array, but I only wanted to remove validates_uniqueness_of to stay as loosely coupled as possible.
Procs can’t tell much about themselves – they are mostly just there to be called. But I knew from the Rails code that each validation Proc is added from inside the class method of the validation. So, I just had to figure out a way to determine the method context the Proc had been declared in to be able to remove the right one.
I realized I could read variables from the Proc‘s context by doing an eval with the Proc‘s binding applied. I also found an expression somewhere that returned the name of current method by using the stacktrace information in caller.
All in all, the solution ended up like this:
module TagsMultiSite
module MetaTagExtensions
def self.included(base)
base.extend(ClassMethods)
base.class_eval {
# HACK: Remove the existing validates_uniqueness_of block
read_inheritable_attribute(:validate).reject! do |proc|
if proc.is_a?(Proc)
method = eval("caller[0] =~ /`([^']*)'/ and $1", proc.binding).to_sym rescue nil # Returns the name of method the proc was declared in
:validates_uniqueness_of == method
else
false
end
end
# Add new validates_uniqueness_of with correct scope
validates_uniqueness_of :name, :case_sensitive => false, :scope => :site_id
}
end
end
end
It would be easy to make this into a generalized method for removing Rails validations, but I think this issue is pretty rare. Usually people can just change or remove the original validation. Still, this example demonstrates fairly well how the trusting nature of Ruby allow us to make far-fetched metaprogramming hacks to solve our problems.
Hello, I'm Casper Fabricius. I have developed for the web for 10 years, and have been enjoying Ruby on Rails for the past 5.
My experience covers communities, shopping solutions, multi-language sites, heavy back-end lifting and a wide selection of more traditional websites. I like to integrate Ruby with Java and .NET through JRuby and IronRuby when it makes sense. I am passionate about test- and behavior-driven development, but at the same time I am pragmatic and believe in getting things done.
I live in Copenhagen, Denmark, where I work for a fantastic company: Podio. I do not currently take on freelance assignments.