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.