Sunday, July 20, 2008

eager loading + limit + order in named scope = ActiveRecord 2.1.0 bug

I found this bug while writing my pagination plugin that uses named scopes. It's caused by an oversight in construct_finder_sql_for_association_limiting and manifests when the following three conditions are met:

  • there is a has_many association being eager loaded
  • the query is being limited
  • there is order specified by a named scope

I fixed the bug by making modifications to construct_finder_sql_for_association_limiting. You can apply the fix by including the following code into your Rails project:

module ActiveRecord
  module Associations
    module ClassMethods

      def construct_finder_sql_for_association_limiting(options, join_dependency)
        scope       = scope(:find) || {}
        order       = [options[:order], scope[:order]].compact.join(', ')

        # Only join tables referenced in order or conditions since this is particularly slow on the pre-query.
        tables_from_conditions = conditions_tables(options)
        tables_from_order      = order_tables(options)
        all_tables             = tables_from_conditions + tables_from_order
        distinct_join_associations = all_tables.uniq.map{|table|
          join_dependency.joins_for_table_name(table)
        }.flatten.compact.uniq

        is_distinct = !options[:joins].blank? || include_eager_conditions?(options, tables_from_conditions) || include_eager_order?(options, tables_from_order)
        sql = "SELECT "
        if is_distinct
          sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", order)
        else
          sql << primary_key
        end
        sql << " FROM #{connection.quote_table_name table_name} "

        if is_distinct
          sql << distinct_join_associations.collect(&:association_join).join
          add_joins!(sql, options, scope)
        end

        add_conditions!(sql, options[:conditions], scope)
        add_group!(sql, options[:group], scope)

        if order and is_distinct
          connection.add_order_by_for_association_limiting!(sql, :order => order)
        else
          add_order!(sql, options[:order], scope)
        end

        add_limit!(sql, options, scope)

        return sanitize_sql(sql)
      end
      
    end
  end
end

If you are curious about what exactly was changed, see this diff pastie.

1 comments:

Christopher J. Bottaro said...

Heh, I completely forgot that I made this post.

It's a useless post anyway... the Rails core team fixed this bug before I could even submit the patch.