diff options
author | Gonçalo Silva <goncalossilva@gmail.com> | 2011-03-24 17:21:17 +0000 |
---|---|---|
committer | Gonçalo Silva <goncalossilva@gmail.com> | 2011-03-24 17:21:17 +0000 |
commit | 9887f238871bb2dd73de6ce8855615bcc5d8d079 (patch) | |
tree | 74fa9ff9524a51701cfa23f708b3f777c65b7fe5 /activerecord/lib/active_record/relation/query_methods.rb | |
parent | aff821508a16245ebc03510ba29c70379718dfb7 (diff) | |
parent | 5214e73850916de3c9127d35a4ecee0424d364a3 (diff) | |
download | rails-9887f238871bb2dd73de6ce8855615bcc5d8d079.tar.gz rails-9887f238871bb2dd73de6ce8855615bcc5d8d079.tar.bz2 rails-9887f238871bb2dd73de6ce8855615bcc5d8d079.zip |
Merge branch 'master' of https://github.com/rails/rails
Diffstat (limited to 'activerecord/lib/active_record/relation/query_methods.rb')
-rw-r--r-- | activerecord/lib/active_record/relation/query_methods.rb | 314 |
1 files changed, 183 insertions, 131 deletions
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index e71f1cca72..9470e7c6c5 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -6,247 +6,299 @@ module ActiveRecord extend ActiveSupport::Concern attr_accessor :includes_values, :eager_load_values, :preload_values, - :select_values, :group_values, :order_values, :joins_values, :where_values, :having_values, + :select_values, :group_values, :order_values, :joins_values, + :where_values, :having_values, :bind_values, :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, :from_value def includes(*args) - args.reject! { |a| a.blank? } - clone.tap {|r| r.includes_values += args if args.present? } + args.reject! {|a| a.blank? } + + return self if args.empty? + + relation = clone + relation.includes_values = (relation.includes_values + args).flatten.uniq + relation end def eager_load(*args) - clone.tap {|r| r.eager_load_values += args if args.present? } + return self if args.blank? + + relation = clone + relation.eager_load_values += args + relation end def preload(*args) - clone.tap {|r| r.preload_values += args if args.present? } + return self if args.blank? + + relation = clone + relation.preload_values += args + relation end - def select(*args) + def select(value = Proc.new) if block_given? - to_a.select {|*block_args| yield(*block_args) } + to_a.select {|*block_args| value.call(*block_args) } else - clone.tap {|r| r.select_values += args if args.present? } + relation = clone + relation.select_values += Array.wrap(value) + relation end end def group(*args) - clone.tap {|r| r.group_values += args.flatten if args.present? } + return self if args.blank? + + relation = clone + relation.group_values += args.flatten + relation end def order(*args) - clone.tap {|r| r.order_values += args if args.present? } - end + return self if args.blank? - def reorder(*args) - clone.tap {|r| r.order_values = args if args.present? } + relation = clone + relation.order_values += args.flatten + relation end def joins(*args) + return self if args.compact.blank? + + relation = clone + args.flatten! - clone.tap {|r| r.joins_values += args if args.present? } + relation.joins_values += args + + relation + end + + def bind(value) + relation = clone + relation.bind_values += [value] + relation end def where(opts, *rest) - value = build_where(opts, rest) - value ? clone.tap {|r| r.where_values += Array.wrap(value) } : clone + return self if opts.blank? + + relation = clone + relation.where_values += build_where(opts, rest) + relation end def having(*args) - value = build_where(*args) - clone.tap {|r| r.having_values += Array.wrap(value) if value.present? } + return self if args.blank? + + relation = clone + relation.having_values += build_where(*args) + relation end - def limit(value = true) - clone.tap {|r| r.limit_value = value } + def limit(value) + relation = clone + relation.limit_value = value + relation end - def offset(value = true) - clone.tap {|r| r.offset_value = value } + def offset(value) + relation = clone + relation.offset_value = value + relation end def lock(locks = true) + relation = clone + case locks when String, TrueClass, NilClass - clone.tap {|r| r.lock_value = locks || true } + relation.lock_value = locks || true else - clone.tap {|r| r.lock_value = false } + relation.lock_value = false end + + relation end def readonly(value = true) - clone.tap {|r| r.readonly_value = value } + relation = clone + relation.readonly_value = value + relation end - def create_with(value = true) - clone.tap {|r| r.create_with_value = value } + def create_with(value) + relation = clone + relation.create_with_value = value && (@create_with_value || {}).merge(value) + relation end - def from(value = true) - clone.tap {|r| r.from_value = value } + def from(value) + relation = clone + relation.from_value = value + relation end - def extending(*modules, &block) - modules << Module.new(&block) if block_given? - clone.tap {|r| r.send(:apply_modules, *modules) } + def extending(*modules) + modules << Module.new(&Proc.new) if block_given? + + return self if modules.empty? + + relation = clone + relation.send(:apply_modules, modules.flatten) + relation end def reverse_order - order_clause = arel.send(:order_clauses).join(', ') - relation = except(:order) + order_clause = arel.order_clauses - unless order_clauses.blank? - relation.order(reverse_sql_order(order_clause)) - else - relation.order("#{@klass.table_name}.#{@klass.primary_key} DESC") - end + order = order_clause.empty? ? + "#{table_name}.#{primary_key} DESC" : + reverse_sql_order(order_clause).join(', ') + + except(:order).order(Arel.sql(order)) end def arel @arel ||= build_arel end - def custom_join_sql(*joins) - arel = table - joins.each do |join| - next if join.blank? + def build_arel + arel = table.from table - @implicit_readonly = true + build_joins(arel, @joins_values) unless @joins_values.empty? - case join - when Hash, Array, Symbol - if array_of_strings?(join) - join_string = join.join(' ') - arel = arel.join(join_string) - end - else - arel = arel.join(join) - end - end - arel.joins(arel) - end + collapse_wheres(arel, (@where_values - ['']).uniq) - def build_arel - arel = table + arel.having(*@having_values.uniq.reject{|h| h.blank?}) unless @having_values.empty? - arel = build_joins(arel, @joins_values) unless @joins_values.empty? + arel.take(connection.sanitize_limit(@limit_value)) if @limit_value + arel.skip(@offset_value) if @offset_value - @where_values.uniq.each do |where| - next if where.blank? + arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty? - case where - when Arel::SqlLiteral - arel = arel.where(where) - else - sql = where.is_a?(String) ? where : where.to_sql - arel = arel.where(Arel::SqlLiteral.new("(#{sql})")) - end - end + arel.order(*@order_values.uniq.reject{|o| o.blank?}) unless @order_values.empty? - arel = arel.having(*@having_values.uniq.select{|h| h.present?}) unless @having_values.empty? + build_select(arel, @select_values.uniq) - arel = arel.take(@limit_value) if @limit_value - arel = arel.skip(@offset_value) if @offset_value + arel.from(@from_value) if @from_value + arel.lock(@lock_value) if @lock_value - arel = arel.group(*@group_values.uniq.select{|g| g.present?}) unless @group_values.empty? + arel + end - arel = arel.order(*@order_values.uniq.select{|o| o.present?}) unless @order_values.empty? + private - arel = build_select(arel, @select_values.uniq) + def custom_join_ast(table, joins) + joins = joins.reject { |join| join.blank? } - arel = arel.from(@from_value) if @from_value - arel = arel.lock(@lock_value) if @lock_value + return [] if joins.empty? - arel + @implicit_readonly = true + + joins.map do |join| + case join + when Array + join = Arel.sql(join.join(' ')) if array_of_strings?(join) + when String + join = Arel.sql(join) + end + table.create_string_join(join) + end + end + + def collapse_wheres(arel, wheres) + equalities = wheres.grep(Arel::Nodes::Equality) + + arel.where(Arel::Nodes::And.new(equalities)) unless equalities.empty? + + (wheres - equalities).each do |where| + where = Arel.sql(where) if String === where + arel.where(Arel::Nodes::Grouping.new(where)) + end end def build_where(opts, other = []) case opts when String, Array - @klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other)) + [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))] when Hash attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts) - PredicateBuilder.new(table.engine).build_from_hash(attributes, table) + PredicateBuilder.build_from_hash(table.engine, attributes, table) else - opts + [opts] end end - private - - def build_joins(relation, joins) - joined_associations = [] - association_joins = [] - - joins = @joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq - - joins.each do |join| - association_joins << join if [Hash, Array, Symbol].include?(join.class) && !array_of_strings?(join) + def build_joins(manager, joins) + buckets = joins.group_by do |join| + case join + when String + 'string_join' + when Hash, Symbol, Array + 'association_join' + when ActiveRecord::Associations::JoinDependency::JoinAssociation + 'stashed_join' + when Arel::Nodes::Join + 'join_node' + else + raise 'unknown class: %s' % join.class.name + end end - stashed_association_joins = joins.select {|j| j.is_a?(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)} + association_joins = buckets['association_join'] || [] + stashed_association_joins = buckets['stashed_join'] || [] + join_nodes = buckets['join_node'] || [] + string_joins = (buckets['string_join'] || []).map { |x| + x.strip + }.uniq - non_association_joins = (joins - association_joins - stashed_association_joins) - custom_joins = custom_join_sql(*non_association_joins) + join_list = custom_join_ast(manager, string_joins) - join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins) + join_dependency = ActiveRecord::Associations::JoinDependency.new( + @klass, + association_joins, + join_list + ) + + join_nodes.each do |join| + join_dependency.alias_tracker.aliased_name_for(join.left.name.downcase) + end join_dependency.graft(*stashed_association_joins) @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty? - to_join = [] - + # FIXME: refactor this to build an AST join_dependency.join_associations.each do |association| - if (association_relation = association.relation).is_a?(Array) - to_join << [association_relation.first, association.join_class, association.association_join.first] - to_join << [association_relation.last, association.join_class, association.association_join.last] - else - to_join << [association_relation, association.join_class, association.association_join] - end + association.join_to(manager) end - to_join.each do |tj| - unless joined_associations.detect {|ja| ja[0] == tj[0] && ja[1] == tj[1] && ja[2] == tj[2] } - joined_associations << tj - relation = relation.join(tj[0], tj[1]).on(*tj[2]) - end - end + manager.join_sources.concat join_nodes.uniq + manager.join_sources.concat join_list - relation.join(custom_joins) + manager end def build_select(arel, selects) - if selects.present? + unless selects.empty? @implicit_readonly = false - # TODO: fix this ugly hack, we should refactor the callers to get an ARel compatible array. - # Before this change we were passing to ARel the last element only, and ARel is capable of handling an array - if selects.all? {|s| s.is_a?(String) || !s.is_a?(Arel::Expression) } && !(selects.last =~ /^COUNT\(/) - arel.project(*selects) - else - arel.project(selects.last) - end + arel.project(*selects) else - arel.project(@klass.quoted_table_name + '.*') + arel.project(@klass.arel_table[Arel.star]) end end def apply_modules(modules) - values = Array.wrap(modules) - @extensions += values if values.present? - values.each {|extension| extend(extension) } + unless modules.empty? + @extensions += modules + modules.each {|extension| extend(extension) } + end end def reverse_sql_order(order_query) - order_query.split(',').each { |s| - if s.match(/\s(asc|ASC)$/) - s.gsub!(/\s(asc|ASC)$/, ' DESC') - elsif s.match(/\s(desc|DESC)$/) - s.gsub!(/\s(desc|DESC)$/, ' ASC') - else - s.concat(' DESC') - end - }.join(',') + order_query.join(', ').split(',').collect do |s| + s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC') + end end def array_of_strings?(o) |