aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/associations.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/associations.rb')
-rwxr-xr-xactiverecord/lib/active_record/associations.rb93
1 files changed, 32 insertions, 61 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 934beb7d39..47f97718eb 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -516,14 +516,14 @@ module ActiveRecord
#
# Since only one table is loaded at a time, conditions or orders cannot reference tables other than the main one. If this is the case
# Active Record falls back to the previously used LEFT OUTER JOIN based strategy. For example
- #
+ #
# Post.find(:all, :include => [ :author, :comments ], :conditions => ['comments.approved = ?', true])
#
# will result in a single SQL query with joins along the lines of: <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
# <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions like this can have unintended consequences.
# In the above example posts with no approved comments are not returned at all, because the conditions apply to the SQL statement as a whole
# and not just to the association. You must disambiguate column references for this fallback to happen, for example
- # <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not.
+ # <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not.
#
# If you do want eagerload only some members of an association it is usually more natural to <tt>:include</tt> an association
# which has conditions defined on it:
@@ -557,10 +557,10 @@ module ActiveRecord
#
# Address.find(:all, :include => :addressable)
#
- # will execute one query to load the addresses and load the addressables with one query per addressable type.
+ # will execute one query to load the addresses and load the addressables with one query per addressable type.
# For example if all the addressables are either of class Person or Company then a total of 3 queries will be executed. The list of
# addressable types to load is determined on the back of the addresses loaded. This is not supported if Active Record has to fallback
- # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent
+ # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent
# model's type is a column value so its corresponding table name cannot be put in the +FROM+/+JOIN+ clauses of that query.
#
# == Table Aliasing
@@ -875,7 +875,7 @@ module ActiveRecord
# but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
# [:through]
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
- # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a
+ # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a
# <tt>has_one</tt> or <tt>belongs_to</tt> association on the join model.
# [:source]
# Specifies the source association name used by <tt>has_one :through</tt> queries. Only use it if the name cannot be
@@ -1132,8 +1132,8 @@ module ActiveRecord
# the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
# [:conditions]
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
- # SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are scoped if a hash is used.
- # <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt>
+ # SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are scoped if a hash is used.
+ # <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt>
# or <tt>@blog.posts.build</tt>.
# [:order]
# Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
@@ -1346,12 +1346,12 @@ module ActiveRecord
"#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)"
)
end
-
+
def add_touch_callbacks(reflection, touch_attribute)
method_name = "belongs_to_touch_after_save_or_destroy_for_#{reflection.name}".to_sym
define_method(method_name) do
association = send(reflection.name)
-
+
if touch_attribute == true
association.touch unless association.nil?
else
@@ -1563,7 +1563,7 @@ module ActiveRecord
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
-
+
if reflection.association_foreign_key == reflection.primary_key_name
raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection)
end
@@ -1595,24 +1595,28 @@ module ActiveRecord
def construct_finder_sql_with_included_associations(options, join_dependency)
scope = scope(:find)
- sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
- sql << join_dependency.join_associations.collect{|join| join.association_join }.join
- add_joins!(sql, options[:joins], scope)
- add_conditions!(sql, options[:conditions], scope)
- add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
+ joins = join_dependency.join_associations.collect{|join| join.association_join }.join
+ joins << construct_join(options[:joins], scope)
+
+ conditions = construct_conditions(options[:conditions], scope) || ''
+ conditions << construct_limited_ids_condition(conditions, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
+
+ arel_table((scope && scope[:from]) || options[:from])
+ join(joins)
+ where(conditions)
+ project(column_aliases(join_dependency))
+ group(construct_group(options[:group], options[:having], scope))
+ order(construct_order(options[:order], scope))
- add_group!(sql, options[:group], options[:having], scope)
- add_order!(sql, options[:order], scope)
- add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
- add_lock!(sql, options, scope)
+ take(construct_limit(options[:limit], scope)) if using_limitable_reflections?(join_dependency.reflections)
- return sanitize_sql(sql)
+ return sanitize_sql(arel_relation.to_sql)
end
- def add_limited_ids_condition!(sql, options, join_dependency)
+ def construct_limited_ids_condition(where, options, join_dependency)
unless (id_list = select_limited_ids_list(options, join_dependency)).empty?
- sql << "#{condition_word(sql)} #{connection.quote_table_name table_name}.#{primary_key} IN (#{id_list}) "
+ "#{where.blank? ? '' : ' AND '} #{connection.quote_table_name table_name}.#{primary_key} IN (#{id_list}) "
else
throw :invalid_query
end
@@ -1628,47 +1632,18 @@ module ActiveRecord
end
def construct_finder_sql_for_association_limiting(options, join_dependency)
- scope = scope(:find)
-
# 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|
+ options[:joins] = all_tables.uniq.map {|table|
join_dependency.joins_for_table_name(table)
- }.flatten.compact.uniq
-
- order = options[:order]
- if scoped_order = (scope && scope[:order])
- order = order ? "#{order}, #{scoped_order}" : scoped_order
- end
-
- 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} "
+ }.flatten.compact.uniq.collect { |assoc| assoc.association_join }.join
- if is_distinct
- sql << distinct_join_associations.collect { |assoc| assoc.association_join }.join
- add_joins!(sql, options[:joins], scope)
- end
-
- add_conditions!(sql, options[:conditions], scope)
- add_group!(sql, options[:group], options[:having], scope)
-
- if order && 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)
+ construct_finder_sql(options.merge(
+ :select => connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", construct_order(options[:order], scope(:find)).join(","))
+ )
+ )
end
def tables_in_string(string)
@@ -1772,10 +1747,6 @@ module ActiveRecord
end
end
- def condition_word(sql)
- sql =~ /where/i ? " AND " : "WHERE "
- end
-
def create_extension_modules(association_id, block_extension, extensions)
if block_extension
extension_module_name = "#{self.to_s.demodulize}#{association_id.to_s.camelize}AssociationExtension"