No, I don't work in NYC, DC, or the valley, and I'm cool with that.
If you updated MetaWhere recently, and your application started raising MetaWhereInAssociationErrors, I’m sorry. But not too sorry. Because that error probably saved you from running into strange, confusing problems later. Let me explain.
If you’re a typical MetaWhere user, you’ve gotten into the habit of specifying your conditions with the improved syntax pretty quickly, and they are almost second nature to you now. They certainly were to me, at least.
So much so, in fact, that in my last project, without thinking, I had declared this in an association:
has_many :optional_photos, :class_name => "Photo", :conditions => {:subtype.matches => 'optional%'}
This was a silly mistake on my part, but I’d gotten so used to using MetaWhere it didn’t hit me until later, when none of my optional photos were showing up. I was confused for a moment. I checked the log to see this query:
SELECT COUNT(*) AS count_id FROM (SELECT 1 FROM `assets` WHERE (`assets`.`type` = 'Photo') AND (`assets`.project_id = 2 AND (`assets`.`subtype` = 'optional%'))) AS subquery
Did you spot the problem? My intended LIKE query was being converted to an = query. This happens in activerecord/lib/active_record/reflection.rb, in Association#dependent_conditions:
def dependent_conditions(record, base_class, extra_conditions) dependent_conditions = [] dependent_conditions << "#{primary_key_name} = #{record.send(name).send(:owner_quoted_id)}" dependent_conditions << "#{options[:as]}_type = '#{base_class.name}'" if options[:as] dependent_conditions << klass.send(:sanitize_sql, options[:conditions]) if options[:conditions] dependent_conditions << extra_conditions if extra_conditions dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ") dependent_conditions = dependent_conditions.gsub('@', '\@') dependent_conditions end
The important part is the line that calls sanitize_sql on options[:conditions]. That method converts hashes to standard equality conditions.
“So, why not make sanitize_sql understand MetaWhere conditions? Isn’t that the solution?” I’m glad you (okay, I) asked. Because it’s completely reasonable for the existing behavior to occur in associations.
One of the key reasons you would choose to use an association over a plain instance method to access associated records is that they bake in some special magic that allows you to create new instances of the associated model. For instance, to build a new instance of a comment associated with the first article:
ruby-1.9.2-p0 > Article.first.comments.build(:body => 'hey!') => #<Comment id: nil, article_id: 1, body: "hey!", created_at: nil, updated_at: nil>
Let’s assume, for a moment, a has_many association that looks like this, though (note: Don’t do this. MetaWhere won’t let you now, anyway):
has_many :old_comments, :class_name => "Comment", :conditions => {:created_at.lt => "2009-01-01".to_time}
What would constitute the “correct” way to create a new record, now? To set the created_at value to 1 second before New Year’s Day, 2009? The beginning of time? It doesn’t really make any sense.
This is why MetaWhere will now check whether you are trying to use MetaWhere conditions in an association macro, and raise the error. Better to stop in your tracks before you go too far down that road. That road leads to madness.
In my case, since I was only using that association to load optional photos, and not create them, I went with an instance method:
def optional_photos assets.where(:type => 'Photo', :subtype.matches => 'optional%') end
If you weren’t intending to find only those objects associated with an instance, then you weren’t looking for an association in the first place, anyway — use a scope instead.
Of course, the error message will tell you as much:
MetaWhere::MetaWhereInAssociationError: The :lame_comments association has a MetaWhere::Column in its :conditions. If you actually needed to access conditions other than equality, then you most likely meant to set up a scope or method, instead. Associations only work with standard equality conditions, since they can be used to create records as well.
I'm Ernie Miller. But then, you probably knew that by looking at the page title, or the URL. I'm a Ruby programmer in Louisville, Kentucky. This blog used to be called "metautonomo.us", which I thought was kind of clever, but nobody, including me, could type it. Lesson learned.
