Associations interpolate_sql() context?

A while ago there was a discussion on interpolate_sql() context in
associations that pointed out that when a :conditions is embedded in
an association declaration, the interpolate_sql() call that evaluates
the generated string (from sanitize_sql) can either be the
AssociationJoin (when eager loading) or the record that that the
association is defined on (for non-eager-loaded association
instantiation). This, essentially, makes the interpolation itself
almost completely useless unless one can guarantee that it will only
be called in one context or the other. And even more problematically,
sanitize_sql is evaluated in a different context (the reflection
class). So, there are a number of problems I'm working on resolving:

1) To be able to ensure the the table_name referenced in a
Hash :conditions (e.g. {:member_id => nil}) will be correct whether
loaded straight or eager-loaded such that there is table_name aliasing
involved (I think I've got this one done with a bit of monkey-patching
on AR::Base and AssociationJoin).

2) Currently there is no way (that I can find) to refer to the
association proxy, the reflection or the parent class of an
association reliably in the :conditions String. Again, the biggest
issue here is the aliasing of table_names in eager-loading, but there
may be other issues. The first fix above covers many cases, but if I
want to refer to two different fields in the association's join table,
I just can't. E.g.

  has_many :source_memberships, {
    :foreign_key => 'member_id', :class_name => 'Membership',
    :conditions => '#{join_table}.group_id =
#{join_table}.source_group_id',
    :include => [:member, :source_group, :group],
    :order => 'memberships.created_at ASC'
  }

So, questions? Does anyone know why interpolate_sql() is sent to the
@owner (in AssociationProxy) and not the @reflection.klass like
sanitize_sql? Has anyone else been frustrated by this, or do people
just not use association :conditions because of this? One strategy
would be to massage table names after the interpolate_sql when
aliasing. This is a bit dangerous though and I'd prefer some
standardized evaluation context.

Prev thread: http://groups.google.com/group/rubyonrails-core/browse_frm/thread/e22d42849dc3fd7a/fb28386e25e66941

leei wrote:

1) To be able to ensure the the table_name referenced in a
Hash :conditions (e.g. {:member_id => nil}) will be correct whether
loaded straight or eager-loaded such that there is table_name aliasing
involved (I think I've got this one done with a bit of monkey-patching
on AR::Base and AssociationJoin).

member_id could be a field in several of the joined tables, so
how do you resolve the ambiguity?

2) Currently there is no way (that I can find) to refer to the
association proxy, the reflection or the parent class of an
association reliably in the :conditions String. Again, the biggest
issue here is the aliasing of table_names in eager-loading, but there
may be other issues. The first fix above covers many cases, but if I
want to refer to two different fields in the association's join table,
I just can't. E.g.

  has_many :source_memberships, {
    :foreign_key => 'member_id', :class_name => 'Membership',
    :conditions => '#{join_table}.group_id =
#{join_table}.source_group_id',
    :include => [:member, :source_group, :group],
    :order => 'memberships.created_at ASC'
  }

Could you further explain the problem with two different fields.

So, questions? Does anyone know why interpolate_sql() is sent to the
@owner (in AssociationProxy) and not the @reflection.klass like
sanitize_sql? Has anyone else been frustrated by this, or do people
just not use association :conditions because of this? One strategy
would be to massage table names after the interpolate_sql when
aliasing. This is a bit dangerous though and I'd prefer some
standardized evaluation context.

Yes, the availability of a consistent variable in both interpolation
contexts, such as "association_table", would solve a lot of problems.

My current solution is to avoid interpolation in these circumstances
and change the :conditions option dynamically.

leei wrote:
> 1) To be able to ensure the the table_name referenced in a
> Hash :conditions (e.g. {:member_id => nil}) will be correct whether
> loaded straight or eager-loaded such that there is table_name aliasing
> involved (I think I've got this one done with a bit of monkey-patching
> on AR::Base and AssociationJoin).

member_id could be a field in several of the joined tables, so
how do you resolve the ambiguity?

The key is that it should be "sanitized" against the class of the
association, but with any table aliasing in effect at the time (from
eager loading) used to specify the table name. Currently it is
sanitized in the correct class, but without reference to any table
name aliasing. My patch/plugin solves that.

> 2) Currently there is no way (that I can find) to refer to the
> association proxy, the reflection or the parent class of an
> association reliably in the :conditions String. Again, the biggest
> issue here is the aliasing of table_names in eager-loading, but there
> may be other issues. The first fix above covers many cases, but if I
> want to refer to two different fields in the association's join table,
> I just can't. E.g.

> has_many :source_memberships, {
> :foreign_key => 'member_id', :class_name => 'Membership',
> :conditions => '#{join_table}.group_id =
> #{join_table}.source_group_id',
> :include => [:member, :source_group, :group],
> :order => 'memberships.created_at ASC'
> }

Could you further explain the problem with two different fields.

The issue is that if I specify the condition using a Hash
(e.g. :group_id => '#{join_table}.source_group_id') then the group_id
will be associated with the 'correct' table (and with my patch, even
in the presence of aliasing), but because the String is interpolated
in the @owner context, it has no way of accessing either the
reflection or association objects to get at the 'right' table name. If
the interpolation happened in the @sender (like the sanitize) then it
would be easier to use the same solution as I used for the
sanitize_sql, which was to temporarily override table_name.

> So, questions? Does anyone know why interpolate_sql() is sent to the
> @owner (in AssociationProxy) and not the @reflection.klass like
> sanitize_sql? Has anyone else been frustrated by this, or do people
> just not use association :conditions because of this? One strategy
> would be to massage table names after the interpolate_sql when
> aliasing. This is a bit dangerous though and I'd prefer some
> standardized evaluation context.

Yes, the availability of a consistent variable in both interpolation
contexts, such as "association_table", would solve a lot of problems.

My current solution is to avoid interpolation in these circumstances
and change the :conditions option dynamically.

Huh? How do you do that?

So, questions? Does anyone know why interpolate_sql() is sent to the
@owner (in AssociationProxy) and not the @reflection.klass like
sanitize_sql? Has anyone else been frustrated by this, or do people
just not use association :conditions because of this? One strategy
would be to massage table names after the interpolate_sql when
aliasing. This is a bit dangerous though and I'd prefer some
standardized evaluation context.

We're looking into completely rejigging the query generation in 2.2 by
using something based on relational algebra rather than slinging
strings around. That will make all this stuff about a billion times
easier. My gut feeling is that the fix for this kind of issue is
better handled in that work, rather than throwing a few more oddities
into the current associations code :).

However if there's some simple fixes or enhancements we can do in the
meantime, we can look at them each in turn.