diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2010-01-14 13:36:33 +0530 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2010-01-16 00:02:46 +0530 |
commit | bed9179aa1496f6d28891cf515af0d7e515ebbab (patch) | |
tree | b0c551489b45f93f66c0d726c55fe000b59803ea /activerecord/lib | |
parent | 1c30ec23fef2479cd037945e57a74e5c89c9ece1 (diff) | |
download | rails-bed9179aa1496f6d28891cf515af0d7e515ebbab.tar.gz rails-bed9179aa1496f6d28891cf515af0d7e515ebbab.tar.bz2 rails-bed9179aa1496f6d28891cf515af0d7e515ebbab.zip |
Make scopes use relations under the hood
Diffstat (limited to 'activerecord/lib')
11 files changed, 329 insertions, 269 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 50fd4aafec..d6ecd0a5e3 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1703,24 +1703,30 @@ module ActiveRecord end def construct_finder_arel_with_included_associations(options, join_dependency) - scope = scope(:find) - relation = active_relation for association in join_dependency.join_associations relation = association.join_relation(relation) end - relation = relation.joins(construct_join(options[:joins], scope)). + relation = relation.joins(options[:joins]). select(column_aliases(join_dependency)). - group(options[:group] || (scope && scope[:group])). - having(options[:having] || (scope && scope[:having])). - order(construct_order(options[:order], scope)). - where(construct_conditions(options[:conditions], scope)). - from((scope && scope[:from]) || options[:from]) + group(options[:group]). + having(options[:having]). + order(options[:order]). + where(options[:conditions]). + from(options[:from]) + + scoped_relation = current_scoped_methods + scoped_relation_limit = scoped_relation.taken if scoped_relation + + relation = current_scoped_methods.except(:limit).merge(relation) if current_scoped_methods - relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) - relation = relation.limit(construct_limit(options[:limit], scope)) if using_limitable_reflections?(join_dependency.reflections) + if !using_limitable_reflections?(join_dependency.reflections) && ((scoped_relation && scoped_relation.taken) || options[:limit]) + relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency)) + end + + relation = relation.limit(options[:limit] || scoped_relation_limit) if using_limitable_reflections?(join_dependency.reflections) relation end @@ -1748,23 +1754,23 @@ module ActiveRecord end def construct_finder_sql_for_association_limiting(options, join_dependency) - scope = scope(:find) - relation = active_relation for association in join_dependency.join_associations relation = association.join_relation(relation) end - relation = relation.joins(construct_join(options[:joins], scope)). - where(construct_conditions(options[:conditions], scope)). - group(options[:group] || (scope && scope[:group])). - having(options[:having] || (scope && scope[:having])). - order(construct_order(options[:order], scope)). - limit(construct_limit(options[:limit], scope)). - offset(construct_limit(options[:offset], scope)). - from(options[:from]). - select(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", construct_order(options[:order], scope(:find)).join(","))) + relation = relation.joins(options[:joins]). + where(options[:conditions]). + group(options[:group]). + having(options[:having]). + order(options[:order]). + limit(options[:limit]). + offset(options[:offset]). + from(options[:from]) + + relation = current_scoped_methods.except(:select, :includes, :eager_load).merge(relation) if current_scoped_methods + relation = relation.select(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", options[:order])) relation.to_sql end @@ -2030,118 +2036,85 @@ module ActiveRecord def association_join return @join if @join - connection = reflection.active_record.connection + + aliased_table = Arel::Table.new(table_name, :as => @aliased_table_name, :engine => active_relation_engine) + parent_table = Arel::Table.new(parent.table_name, :as => parent.aliased_table_name, :engine => active_relation_engine) + @join = case reflection.macro - when :has_and_belongs_to_many - ["%s.%s = %s.%s " % [ - connection.quote_table_name(aliased_join_table_name), - options[:foreign_key] || reflection.active_record.to_s.foreign_key, - connection.quote_table_name(parent.aliased_table_name), - reflection.active_record.primary_key], - "%s.%s = %s.%s " % [ - connection.quote_table_name(aliased_table_name), - klass.primary_key, - connection.quote_table_name(aliased_join_table_name), - options[:association_foreign_key] || klass.to_s.foreign_key - ] - ] - when :has_many, :has_one - if reflection.options[:through] - jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil - first_key = second_key = as_extra = nil - - if through_reflection.options[:as] # has_many :through against a polymorphic join - jt_foreign_key = through_reflection.options[:as].to_s + '_id' - jt_as_extra = " AND %s.%s = %s" % [ - connection.quote_table_name(aliased_join_table_name), - connection.quote_column_name(through_reflection.options[:as].to_s + '_type'), - klass.quote_value(parent.active_record.base_class.name) - ] + when :has_and_belongs_to_many + join_table = Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => active_relation_engine) + fk = options[:foreign_key] || reflection.active_record.to_s.foreign_key + klass_fk = options[:association_foreign_key] || klass.to_s.foreign_key + + [ + join_table[fk].eq(parent_table[reflection.active_record.primary_key]), + aliased_table[klass.primary_key].eq(join_table[klass_fk]) + ] + when :has_many, :has_one + if reflection.options[:through] + join_table = Arel::Table.new(through_reflection.klass.table_name, :as => aliased_join_table_name, :engine => active_relation_engine) + jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil + first_key = second_key = as_extra = nil + + if through_reflection.options[:as] # has_many :through against a polymorphic join + jt_foreign_key = through_reflection.options[:as].to_s + '_id' + jt_as_extra = join_table[through_reflection.options[:as].to_s + '_type'].eq(parent.active_record.base_class.name) + else + jt_foreign_key = through_reflection.primary_key_name + end + + case source_reflection.macro + when :has_many + if source_reflection.options[:as] + first_key = "#{source_reflection.options[:as]}_id" + second_key = options[:foreign_key] || primary_key + as_extra = aliased_table["#{source_reflection.options[:as]}_type"].eq(source_reflection.active_record.base_class.name) else - jt_foreign_key = through_reflection.primary_key_name + first_key = through_reflection.klass.base_class.to_s.foreign_key + second_key = options[:foreign_key] || primary_key end - case source_reflection.macro - when :has_many - if source_reflection.options[:as] - first_key = "#{source_reflection.options[:as]}_id" - second_key = options[:foreign_key] || primary_key - as_extra = " AND %s.%s = %s" % [ - connection.quote_table_name(aliased_table_name), - connection.quote_column_name("#{source_reflection.options[:as]}_type"), - klass.quote_value(source_reflection.active_record.base_class.name) - ] - else - first_key = through_reflection.klass.base_class.to_s.foreign_key - second_key = options[:foreign_key] || primary_key - end - - unless through_reflection.klass.descends_from_active_record? - jt_sti_extra = " AND %s.%s = %s" % [ - connection.quote_table_name(aliased_join_table_name), - connection.quote_column_name(through_reflection.active_record.inheritance_column), - through_reflection.klass.quote_value(through_reflection.klass.sti_name)] - end - when :belongs_to - first_key = primary_key - if reflection.options[:source_type] - second_key = source_reflection.association_foreign_key - jt_source_extra = " AND %s.%s = %s" % [ - connection.quote_table_name(aliased_join_table_name), - connection.quote_column_name(reflection.source_reflection.options[:foreign_type]), - klass.quote_value(reflection.options[:source_type]) - ] - else - second_key = source_reflection.primary_key_name - end + unless through_reflection.klass.descends_from_active_record? + jt_sti_extra = join_table[through_reflection.active_record.inheritance_column].eq(through_reflection.klass.sti_name) + end + when :belongs_to + first_key = primary_key + if reflection.options[:source_type] + second_key = source_reflection.association_foreign_key + jt_source_extra = join_table[reflection.source_reflection.options[:foreign_type]].eq(reflection.options[:source_type]) + else + second_key = source_reflection.primary_key_name end - - ["(%s.%s = %s.%s%s%s%s) " % [ - connection.quote_table_name(parent.aliased_table_name), - connection.quote_column_name(parent.primary_key), - connection.quote_table_name(aliased_join_table_name), - connection.quote_column_name(jt_foreign_key), - jt_as_extra, jt_source_extra, jt_sti_extra], - "(%s.%s = %s.%s%s) " % [ - connection.quote_table_name(aliased_table_name), - connection.quote_column_name(first_key), - connection.quote_table_name(aliased_join_table_name), - connection.quote_column_name(second_key), - as_extra] - ] - - elsif reflection.options[:as] - "%s.%s = %s.%s AND %s.%s = %s" % [ - connection.quote_table_name(aliased_table_name), - "#{reflection.options[:as]}_id", - connection.quote_table_name(parent.aliased_table_name), - parent.primary_key, - connection.quote_table_name(aliased_table_name), - "#{reflection.options[:as]}_type", - klass.quote_value(parent.active_record.base_class.name) - ] - else - foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key - "%s.%s = %s.%s " % [ - aliased_table_name, - foreign_key, - parent.aliased_table_name, - reflection.options[:primary_key] || parent.primary_key - ] end - when :belongs_to - "%s.%s = %s.%s " % [ - connection.quote_table_name(aliased_table_name), - reflection.klass.primary_key, - connection.quote_table_name(parent.aliased_table_name), - options[:foreign_key] || reflection.primary_key_name + + [ + [parent_table[parent.primary_key].eq(join_table[jt_foreign_key]), jt_as_extra, jt_source_extra, jt_sti_extra].reject{|x| x.blank? }, + aliased_table[first_key].eq(join_table[second_key]) ] + elsif reflection.options[:as] + id_rel = aliased_table["#{reflection.options[:as]}_id"].eq(parent_table[parent.primary_key]) + type_rel = aliased_table["#{reflection.options[:as]}_type"].eq(parent.active_record.base_class.name) + [id_rel, type_rel] + else + foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key + [aliased_table[foreign_key].eq(parent_table[reflection.options[:primary_key] || parent.primary_key])] + end + when :belongs_to + [aliased_table[reflection.klass.primary_key].eq(parent_table[options[:foreign_key] || reflection.primary_key_name])] + end + + unless klass.descends_from_active_record? + sti_column = aliased_table[klass.inheritance_column] + sti_condition = sti_column.eq(klass.sti_name) + klass.send(:subclasses).each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) } + + @join << sti_condition end - @join << %(AND %s) % [ - klass.send(:type_condition, aliased_table_name)] unless klass.descends_from_active_record? [through_reflection, reflection].each do |ref| - @join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name))} " if ref && ref.options[:conditions] + if ref && ref.options[:conditions] + @join << interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name)) + end end @join diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 358db6df1d..64dd5cf629 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -58,11 +58,14 @@ module ActiveRecord find_scope = construct_scope[:find].slice(:conditions, :order) with_scope(:find => find_scope) do - relation = @reflection.klass.send(:construct_finder_arel, options) + relation = @reflection.klass.send(:construct_finder_arel, options, @reflection.klass.send(:current_scoped_methods)) case args.first - when :first, :last, :all + when :first, :last relation.send(args.first) + when :all + records = relation.all + @reflection.options[:uniq] ? uniq(records) : records else relation.find(*args) end @@ -402,7 +405,7 @@ module ActiveRecord end elsif @reflection.klass.scopes.include?(method) @reflection.klass.scopes[method].call(self, *args) - else + else with_scope(construct_scope) do if block_given? @reflection.klass.send(method, *args) { |*block_args| yield(*block_args) } diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 214ce5959a..387b85aacd 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -51,8 +51,6 @@ module ActiveRecord end def construct_find_options!(options) - options[:select] = construct_select(options[:select]) - options[:from] ||= construct_from options[:joins] = construct_joins(options[:joins]) options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include] end diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb index 1924156e2a..1001199daa 100644 --- a/activerecord/lib/active_record/associations/through_association_scope.rb +++ b/activerecord/lib/active_record/associations/through_association_scope.rb @@ -6,8 +6,7 @@ module ActiveRecord def construct_scope { :create => construct_owner_attributes(@reflection), - :find => { :from => construct_from, - :conditions => construct_conditions, + :find => { :conditions => construct_conditions, :joins => construct_joins, :include => @reflection.options[:include] || @reflection.source_reflection.options[:include], :select => construct_select, diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index cc592aa2d3..7e4ee4b840 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -644,7 +644,7 @@ module ActiveRecord #:nodoc: options = args.extract_options! set_readonly_option!(options) - relation = construct_finder_arel(options) + relation = construct_finder_arel(options, current_scoped_methods) case args.first when :first, :last, :all @@ -870,20 +870,21 @@ module ActiveRecord #:nodoc: # # Update all books that match our conditions, but limit it to 5 ordered by date # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5 def update_all(updates, conditions = nil, options = {}) - scope = scope(:find) - relation = active_relation - if conditions = construct_conditions(conditions, scope) + if conditions = construct_conditions(conditions, nil) relation = relation.where(Arel::SqlLiteral.new(conditions)) end - relation = if options.has_key?(:limit) || (scope && scope[:limit]) + relation = relation.limit(options[:limit]) if options[:limit].present? + relation = relation.order(options[:order]) if options[:order].present? + + if current_scoped_methods && current_scoped_methods.limit_value.present? && current_scoped_methods.order_values.present? # Only take order from scope if limit is also provided by scope, this # is useful for updating a has_many association with a limit. - relation.order(construct_order(options[:order], scope)).limit(construct_limit(options[:limit], scope)) + relation = current_scoped_methods.merge(relation) if current_scoped_methods else - relation.order(options[:order]) + relation = current_scoped_methods.except(:limit, :order).merge(relation) if current_scoped_methods end relation.update(sanitize_sql_for_assignment(updates)) @@ -1572,26 +1573,26 @@ module ActiveRecord #:nodoc: end end - def construct_finder_arel(options = {}, scope = scope(:find)) + def construct_finder_arel(options = {}, scope = nil) validate_find_options(options) relation = active_relation. - joins(construct_join(options[:joins], scope)). - where(construct_conditions(options[:conditions], scope)). - select(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))). - group(options[:group] || (scope && scope[:group])). - having(options[:having] || (scope && scope[:having])). - order(construct_order(options[:order], scope)). - limit(construct_limit(options[:limit], scope)). - offset(construct_offset(options[:offset], scope)). + joins(options[:joins]). + where(options[:conditions]). + select(options[:select]). + group(options[:group]). + having(options[:having]). + order(options[:order]). + limit(options[:limit]). + offset(options[:offset]). from(options[:from]). - includes( merge_includes(scope && scope[:include], options[:include])) - - lock = (scope && scope[:lock]) || options[:lock] - relation = relation.lock if lock.present? + includes(options[:include]) - relation = relation.readonly if options[:readonly] + relation = relation.where(type_condition) if finder_needs_type_condition? + relation = relation.lock(options[:lock]) if options[:lock].present? + relation = relation.readonly(options[:readonly]) if options.has_key?(:readonly) + relation = scope.merge(relation) if scope relation end @@ -1665,10 +1666,10 @@ module ActiveRecord #:nodoc: relation = active_relation.table join_dependency.join_associations.map { |association| if (association_relation = association.relation).is_a?(Array) - [Arel::InnerJoin.new(relation, association_relation.first, association.association_join.first).joins(relation), - Arel::InnerJoin.new(relation, association_relation.last, association.association_join.last).joins(relation)].join() + [Arel::InnerJoin.new(relation, association_relation.first, *association.association_join.first).joins(relation), + Arel::InnerJoin.new(relation, association_relation.last, *association.association_join.last).joins(relation)].join() else - Arel::InnerJoin.new(relation, association_relation, association.association_join).joins(relation) + Arel::InnerJoin.new(relation, association_relation, *association.association_join).joins(relation) end }.join(" ") end @@ -1713,7 +1714,7 @@ module ActiveRecord #:nodoc: super unless all_attributes_exists?(attribute_names) if match.finder? options = arguments.extract_options! - relation = options.any? ? construct_finder_arel(options) : scoped + relation = options.any? ? construct_finder_arel(options, current_scoped_methods) : scoped relation.send :find_by_attributes, match, attribute_names, *arguments elsif match.instantiator? scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block @@ -1830,52 +1831,49 @@ module ActiveRecord #:nodoc: def with_scope(method_scoping = {}, action = :merge, &block) method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping) - # Dup first and second level of hash (method and params). - method_scoping = method_scoping.inject({}) do |hash, (method, params)| - hash[method] = (params == true) ? params : params.dup - hash - end + if method_scoping.is_a?(Hash) + # Dup first and second level of hash (method and params). + method_scoping = method_scoping.inject({}) do |hash, (method, params)| + hash[method] = (params == true) ? params : params.dup + hash + end - method_scoping.assert_valid_keys([ :find, :create ]) + method_scoping.assert_valid_keys([ :find, :create ]) - if f = method_scoping[:find] - f.assert_valid_keys(VALID_FIND_OPTIONS) - set_readonly_option! f - end + if f = method_scoping[:find] + f.assert_valid_keys(VALID_FIND_OPTIONS) + set_readonly_option! f + end - # Merge scopings - if [:merge, :reverse_merge].include?(action) && current_scoped_methods - method_scoping = current_scoped_methods.inject(method_scoping) do |hash, (method, params)| - case hash[method] - when Hash - if method == :find - (hash[method].keys + params.keys).uniq.each do |key| - merge = hash[method][key] && params[key] # merge if both scopes have the same key - if key == :conditions && merge - if params[key].is_a?(Hash) && hash[method][key].is_a?(Hash) - hash[method][key] = merge_conditions(hash[method][key].deep_merge(params[key])) - else - hash[method][key] = merge_conditions(params[key], hash[method][key]) - end - elsif key == :include && merge - hash[method][key] = merge_includes(hash[method][key], params[key]).uniq - elsif key == :joins && merge - hash[method][key] = merge_joins(params[key], hash[method][key]) - else - hash[method][key] = hash[method][key] || params[key] - end - end - else - if action == :reverse_merge - hash[method] = hash[method].merge(params) - else - hash[method] = params.merge(hash[method]) - end - end - else - hash[method] = params + relation = construct_finder_arel(method_scoping[:find] || {}) + + if current_scoped_methods && current_scoped_methods.create_with_value && method_scoping[:create] + scope_for_create = case action + when :merge + current_scoped_methods.create_with_value.merge(method_scoping[:create]) + when :reverse_merge + method_scoping[:create].merge(current_scoped_methods.create_with_value) + else + method_scoping[:create] end - hash + + relation = relation.create_with(scope_for_create) + else + scope_for_create = method_scoping[:create] + scope_for_create ||= current_scoped_methods.create_with_value if current_scoped_methods + relation = relation.create_with(scope_for_create) if scope_for_create + end + + method_scoping = relation + end + + if current_scoped_methods + case action + when :merge + method_scoping = current_scoped_methods.merge(method_scoping) + when :reverse_merge + method_scoping = current_scoped_methods.except(:where).merge(method_scoping) + method_scoping = method_scoping.merge(current_scoped_methods.only(:where)) end end @@ -1904,20 +1902,22 @@ module ActiveRecord #:nodoc: # default_scope :order => 'last_name, first_name' # end def default_scope(options = {}) - self.default_scoping << { :find => options, :create => options[:conditions].is_a?(Hash) ? options[:conditions] : {} } + self.default_scoping << construct_finder_arel(options) end # Test whether the given method and optional key are scoped. def scoped?(method, key = nil) #:nodoc: - if current_scoped_methods && (scope = current_scoped_methods[method]) - !key || !scope[key].nil? + case method + when :create + current_scoped_methods.send(:scope_for_create).present? if current_scoped_methods end end # Retrieve the scope for the given method and optional key. def scope(method, key = nil) #:nodoc: - if current_scoped_methods && (scope = current_scoped_methods[method]) - key ? scope[key] : scope + case method + when :create + current_scoped_methods.send(:scope_for_create) if current_scoped_methods end end diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 20d287faeb..bbb5922e0e 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -46,19 +46,19 @@ module ActiveRecord def count(*args) case args.size when 0 - construct_calculation_arel.count + construct_calculation_arel({}, current_scoped_methods).count when 1 if args[0].is_a?(Hash) options = args[0] distinct = options.has_key?(:distinct) ? options.delete(:distinct) : false - construct_calculation_arel(options).count(options[:select], :distinct => distinct) + construct_calculation_arel(options, current_scoped_methods).count(options[:select], :distinct => distinct) else - construct_calculation_arel.count(args[0]) + construct_calculation_arel({}, current_scoped_methods).count(args[0]) end when 2 column_name, options = args distinct = options.has_key?(:distinct) ? options.delete(:distinct) : false - construct_calculation_arel(options).count(column_name, :distinct => distinct) + construct_calculation_arel(options, current_scoped_methods).count(column_name, :distinct => distinct) else raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}" end @@ -141,7 +141,7 @@ module ActiveRecord # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors # Person.sum("2 * age") def calculate(operation, column_name, options = {}) - construct_calculation_arel(options).calculate(operation, column_name, options.slice(:distinct)) + construct_calculation_arel(options, current_scoped_methods).calculate(operation, column_name, options.slice(:distinct)) rescue ThrowResult 0 end @@ -151,49 +151,74 @@ module ActiveRecord options.assert_valid_keys(CALCULATIONS_OPTIONS) end - def construct_calculation_arel(options = {}) + def construct_calculation_arel(options = {}, merge_with_relation = nil) validate_calculation_options(options) options = options.except(:distinct) - scope = scope(:find) - includes = merge_includes(scope ? scope[:include] : [], options[:include]) + includes = merge_includes(merge_with_relation ? merge_with_relation.includes_values : [], options[:include]) if includes.any? - join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(options[:joins], scope)) - construct_calculation_arel_with_included_associations(options, join_dependency) + merge_with_joins = merge_with_relation ? merge_with_relation.joins_values : [] + joins = (merge_with_joins + Array.wrap(options[:joins])).uniq + join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(joins, nil)) + construct_calculation_arel_with_included_associations(options, join_dependency, merge_with_relation) else - active_relation. - joins(construct_join(options[:joins], scope)). - from((scope && scope[:from]) || options[:from]). - where(construct_conditions(options[:conditions], scope)). + relation = active_relation. + joins(options[:joins]). + where(options[:conditions]). order(options[:order]). limit(options[:limit]). offset(options[:offset]). group(options[:group]). - having(options[:having]). - select(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))) + having(options[:having]) + + if merge_with_relation + relation = merge_with_relation.except(:select, :order, :limit, :offset, :group, :from).merge(relation) + else + relation = relation.where(type_condition) if finder_needs_type_condition? + end + + from = merge_with_relation.from_value if merge_with_relation && merge_with_relation.from_value.present? + from = options[:from] if from.blank? && options[:from].present? + relation = relation.from(from) + + select = options[:select].presence || (merge_with_relation ? merge_with_relation.select_values.join(", ") : nil) + relation = relation.select(select) + + relation end end - def construct_calculation_arel_with_included_associations(options, join_dependency) - scope = scope(:find) - + def construct_calculation_arel_with_included_associations(options, join_dependency, merge_with_relation = nil) relation = active_relation for association in join_dependency.join_associations relation = association.join_relation(relation) end - relation = relation.joins(construct_join(options[:joins], scope)). + if merge_with_relation + relation.joins_values = (merge_with_relation.joins_values + relation.joins_values).uniq + relation.where_values = merge_with_relation.where_values + + merge_limit = merge_with_relation.taken + else + relation = relation.where(type_condition) if finder_needs_type_condition? + end + + relation = relation.joins(options[:joins]). select(column_aliases(join_dependency)). group(options[:group]). having(options[:having]). order(options[:order]). - where(construct_conditions(options[:conditions], scope)). - from((scope && scope[:from]) || options[:from]) + where(options[:conditions]). + from(options[:from]) + + + if !using_limitable_reflections?(join_dependency.reflections) && (merge_limit || options[:limit]) + relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency)) + end - relation = relation.where(construct_arel_limited_ids_condition(options, join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) - relation = relation.limit(construct_limit(options[:limit], scope)) if using_limitable_reflections?(join_dependency.reflections) + relation = relation.limit(options[:limit] || merge_limit) if using_limitable_reflections?(join_dependency.reflections) relation end diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index f63b249241..47b69dec62 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -26,10 +26,12 @@ module ActiveRecord if options.present? Scope.new(self, options, &block) else - unless scoped?(:find) + current_scope = current_scoped_methods + + unless current_scope finder_needs_type_condition? ? active_relation.where(type_condition) : active_relation.spawn else - construct_finder_arel + construct_finder_arel({}, current_scoped_methods) end end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 33127194b0..8a86d2e60a 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -47,17 +47,20 @@ module ActiveRecord @records = if find_with_associations begin - @klass.send(:find_with_associations, { - :select => arel.send(:select_clauses).join(', '), + options = { + :select => @select_values.any? ? @select_values.join(", ") : nil, :joins => arel.joins(arel), - :group => arel.send(:group_clauses).join(', '), + :group => @group_values.any? ? @group_values.join(", ") : nil, :order => order_clause, :conditions => where_clause, :limit => arel.taken, :offset => arel.skipped, :from => (arel.send(:from_clauses) if arel.send(:sources).present?) - }, - ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_values + @includes_values, nil)) + } + + including = (@eager_load_values + @includes_values).uniq + join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, nil) + @klass.send(:find_with_associations, options, join_dependency) rescue ThrowResult [] end @@ -161,7 +164,7 @@ module ActiveRecord end def with_create_scope - @klass.send(:with_scope, :create => scope_for_create) { yield } + @klass.send(:with_scope, :create => scope_for_create, :find => {}) { yield } end def scope_for_create diff --git a/activerecord/lib/active_record/relation/calculation_methods.rb b/activerecord/lib/active_record/relation/calculation_methods.rb index e6f62ee49a..2477481ec8 100644 --- a/activerecord/lib/active_record/relation/calculation_methods.rb +++ b/activerecord/lib/active_record/relation/calculation_methods.rb @@ -40,7 +40,7 @@ module ActiveRecord distinct = options[:distinct] || distinct column_name = :all if column_name.blank? && operation == "count" - if arel.send(:groupings).any? + if @group_values.any? return execute_grouped_calculation(operation, column_name) else return execute_simple_calculation(operation, column_name, distinct) @@ -63,7 +63,7 @@ module ActiveRecord end def execute_grouped_calculation(operation, column_name) #:nodoc: - group_attr = arel.send(:groupings).first.value + group_attr = @group_values.first association = @klass.reflect_on_association(group_attr.to_sym) associated = association && association.macro == :belongs_to # only count belongs_to associations group_field = associated ? association.primary_key_name : group_attr @@ -106,7 +106,6 @@ module ActiveRecord column_name = :all # Handles count(), count(:column), count(:distinct => true), count(:column, :distinct => true) - # TODO : relation.projections only works when .select() was last in the chain. Fix it! case args.size when 0 select = get_projection_name_from_chained_relations diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 7ceec40954..a3ac58bc81 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -10,8 +10,20 @@ module ActiveRecord def #{query_method}(*args) spawn.tap do |new_relation| new_relation.#{query_method}_values ||= [] - value = args.size > 1 ? [args] : Array.wrap(args) - new_relation.#{query_method}_values += value + value = Array.wrap(args.flatten).reject {|x| x.blank? } + new_relation.#{query_method}_values += value if value.present? + end + end + CEVAL + end + + [:where, :having].each do |query_method| + class_eval <<-CEVAL + def #{query_method}(*args) + spawn.tap do |new_relation| + new_relation.#{query_method}_values ||= [] + value = build_where(*args) + new_relation.#{query_method}_values += [*value] if value.present? end end CEVAL @@ -58,51 +70,83 @@ module ActiveRecord def build_arel arel = table - @joins_values.each do |j| - next if j.blank? + joined_associations = [] + association_joins = [] + + joins = @joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq + + # Build association joins first + joins.each do |join| + association_joins << join if [Hash, Array, Symbol].include?(join.class) && !@klass.send(:array_of_strings?, join) + end + + if association_joins.any? + join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins.uniq, nil) + to_join = [] + + join_dependency.join_associations.each do |association| + if (association_relation = association.relation).is_a?(Array) + to_join << [association_relation.first, association.association_join.first] + to_join << [association_relation.last, association.association_join.last] + else + to_join << [association_relation, association.association_join] + end + end + + to_join.each do |tj| + unless joined_associations.detect {|ja| ja[0] == tj[0] && ja[1] == tj[1] } + joined_associations << tj + arel = arel.join(tj[0]).on(*tj[1]) + end + end + end + + joins.each do |join| + next if join.blank? @implicit_readonly = true - case j + case join when Relation::JoinOperation - arel = arel.join(j.relation, j.join_class).on(j.on) + arel = arel.join(join.relation, join.join_class).on(*join.on) when Hash, Array, Symbol - if @klass.send(:array_of_strings?, j) - arel = arel.join(j.join(' ')) - else - arel = arel.join(@klass.send(:build_association_joins, j)) + if @klass.send(:array_of_strings?, join) + join_string = join.join(' ') + arel = arel.join(join_string) end else - arel = arel.join(j) + arel = arel.join(join) end end - @where_values.each do |where| - if conditions = build_where(where) - arel = conditions.is_a?(String) ? arel.where(conditions) : arel.where(*conditions) - end + @where_values.uniq.each do |w| + arel = w.is_a?(String) ? arel.where(w) : arel.where(*w) end - @having_values.each do |where| - if conditions = build_where(where) - arel = conditions.is_a?(String) ? arel.having(conditions) : arel.having(*conditions) - end + @having_values.uniq.each do |h| + arel = h.is_a?(String) ? arel.having(h) : arel.having(*h) end arel = arel.take(@limit_value) if @limit_value.present? arel = arel.skip(@offset_value) if @offset_value.present? - @group_values.each do |g| + @group_values.uniq.each do |g| arel = arel.group(g) if g.present? end - @order_values.each do |o| + @order_values.uniq.each do |o| arel = arel.order(o) if o.present? end - @select_values.each do |s| - @implicit_readonly = false - arel = arel.project(s) if s.present? + selects = @select_values.uniq + + if selects.present? + selects.each do |s| + @implicit_readonly = false + arel = arel.project(s) if s.present? + end + elsif joins.present? + arel = arel.project(@klass.quoted_table_name + '.*') end arel = arel.from(@from_value) if @from_value.present? @@ -120,7 +164,7 @@ module ActiveRecord def build_where(*args) return if args.blank? - builder = PredicateBuilder.new(Arel::Sql::Engine.new(@klass)) + builder = PredicateBuilder.new(table.engine) conditions = if [String, Array].include?(args.first.class) merged = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 66eae69d92..a248c72715 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -19,21 +19,19 @@ module ActiveRecord merged_relation = spawn.eager_load(r.eager_load_values).preload(r.preload_values).includes(r.includes_values) - merged_relation.readonly_value = r.readonly_value unless merged_relation.readonly_value - merged_relation.limit_value = r.limit_value unless merged_relation.limit_value + merged_relation.readonly_value = r.readonly_value unless r.readonly_value.nil? + merged_relation.limit_value = r.limit_value if r.limit_value.present? merged_relation.lock_value = r.lock_value unless merged_relation.lock_value + merged_relation.offset_value = r.offset_value if r.offset_value.present? merged_relation = merged_relation. joins(r.joins_values). group(r.group_values). - offset(r.offset_value). select(r.select_values). from(r.from_value). having(r.having_values) - relation_order = r.order_values - merged_order = relation_order.present? ? relation_order : order_values - merged_relation.order_values = merged_order + merged_relation.order_values = Array.wrap(order_values) + Array.wrap(r.order_values) merged_relation.create_with_value = @create_with_value @@ -50,7 +48,7 @@ module ActiveRecord merged_wheres = merged_wheres.reject {|p| p.is_a?(Arel::Predicates::Equality) && p.operand1.name == w.operand1.name } end - merged_wheres << w + merged_wheres += [w] end merged_relation.where_values = merged_wheres @@ -74,5 +72,21 @@ module ActiveRecord result end + def only(*onlies) + result = Relation.new(@klass, table) + + onlies.each do |only| + if (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).include?(only) + result.send(:"#{only}_values=", send(:"#{only}_values")) + elsif Relation::SINGLE_VALUE_METHODS.include?(only) + result.send(:"#{only}_value=", send(:"#{only}_value")) + else + raise "Invalid argument : #{only}" + end + end + + result + end + end end |