I was hacking the acts_as_taggable_on_steroids plugin to support different categories for tags and taggings and I bumped right into the issue of how to have dynamic conditions for the has_many association. The problem with associations in general is that they are CLASS METHODs, e.g. they are run when the class declaration is being loaded into memory at runtime. However, our condition has to be evaluated at evaluation time. This means if we write (psuedo-ruby code lifted from my hack on the the acts_as_taggable_on_steroids Tag model)
class Tag
has_many :taggings, :conditions => “taggings.category_id= #{self.category_id}”
end
this will very well error out on us. Since, at class loading time, Ruby has no idea about the particular category_id attribute of the “self” instance of the Tag object. On his blog, Dweedb proposed a solution of creating a new souped-up has_many association, e.g. has_many_with_args. I wasn’t convinced about this solution since it means I may very well end up with writing/extending every association within Rails with something else if I need to support some special dynamic conditions.
I was playing with lambda function (e.g. :conditions => Proc.new { “some_clever_dynamic_conditions_here”} ) but for some reasons I kept getting the “calling private gsub method” error for the Proc when ActiveRecord was interpolating (constructing?) the SQL. Nonetheless, I got an important hint from the Dweedb’s post about the use of single quote on the :conditions to keep the it as string literal and delay the evaluation of the conditions till, well, evaluation time. “Excelente!”, as my friend Javier would shout in Espanol!
The solution is made possible by the use of the native Ruby #send(:action, *args) method to read the attribute of the model instance. Since if we wrap the conditions around the single quotes, when the model class is being loaded to the Ruby VM, everything still remains as-is. Only at runtime, when ActiveRecord is busy substituting the conditions string into the generated SQL statement, it inadvertently executes the code inside, which then invokes the send() method on the current instance to return the value of the attribute (You know what just popped into my mind? HOLY SMOKE! IT IS CODE-INJECTION through SQL-INJECTION!)
The updated code for the new Tag model is
has_many :taggings, :conditions => ‘#{Tagging.table_name}.category_id = #{self.send(:category_id)}’
Anyway, I was able to get all the tags and their associated taggins from the same category, dynamically and hacklessly. With this approach, any conditions for the various associations can be dynamicalized using single quotes and the (god)send method. Hurray!