In looking at (and trying to use) a has_one association, I noticed that the eager loading code for has one associations doesn't respect the "limit one" per record, and could potentially pull back millions of records (though there is code to make sure only one object per association is actually turned converted to an ActiveRecord object).
Since I always use Postgresql with my Rails projects nowadays, I naturally assume that a subselect is the way to go here, but I don't know where sqllite is with subselects. I know MySQL now uses them, but I don't know if Rails is keeping to some kind of lowest common database denominator. If not, I think it might be worth it to try to add some logic to ActiveRecord::AssociationPreload#find_associated_records, but I don't want to get into a whole testing, branching thing if this is too database-specific.
The code I'm envisioning would be something like this:
<code> def min_or_max_from_order(reflection) if reflection.nil? || reflection.options[:order].blank? return 'MAX' end case (reflection.options[:order].split.compact.last.upcase rescue 'ASC') when 'DESC' 'MAX' else 'MIN' end end
def order_column_from_reflection(reflection) if reflection.nil? || reflection.options[:order].blank? return "id" end reflection.options[:order].split.compact.first.split('.').last end
def find_associated_records(ids, reflection, preload_options) options = reflection.options table_name = reflection.klass.quoted_table_name
if interface = reflection.options[:as] conditions = "#{reflection.klass.quoted_table_name}.# {connection.quote_column_name "#{interface}_id"} #{in_or_equals_for_ids (ids)} and #{reflection.klass.quoted_table_name}.# {connection.quote_column_name "#{interface}_type"} = '# {self.base_class.sti_name}'" else foreign_key = reflection.primary_key_name conditions = "#{reflection.klass.quoted_table_name}.# {foreign_key} #{in_or_equals_for_ids(ids)}" end
if reflection.macro == :has_one if reflection.options[:order] # we'll try subselects here select = " #{reflection.klass.quoted_table_name}.# {foreign_key} as __original_key, #{table_name}.* " conditions << " and ( #{reflection.klass.quoted_table_name}.# {order_column_from_reflection(reflection)} = (select #{min_or_max_from_order(reflection)} (\"subselect_table1\".#{order_column_from_reflection(reflection)}) from #{reflection.klass.quoted_table_name} as \"subselect_table1\" where \"subselect_table1\".#{foreign_key} = # {reflection.klass.quoted_table_name}.#{foreign_key} " conditions << append_conditions if reflection.options [:conditions] conditions << " ) ) " end end
conditions << append_conditions(reflection, preload_options)
reflection.klass.with_exclusive_scope do reflection.klass.find(:all, :select => (preload_options[:select] || options[:select] || "#{table_name}.*"), :include => preload_options[:include] || options[:include], :conditions => [conditions, ids], :joins => options[:joins], :group => preload_options[:group] || options[:group], :order => preload_options[:order] || options[:order]) end end </code>
Clearly this wouldn't solve all cases, and some restrictions would have to be laid out in the docs, especially about valid "order by" phrasing, and resolving table name issues in adding the reflection conditions to the subselect conditions...
But anyone have any thoughts about this?