Why MetaWhere 0.9.2 raises errors (and why you'll thank me later)

Comments

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.

The bug hunt

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

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.

If not an association, then what?

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.

comments powered by Disqus