diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2010-01-12 22:20:53 +0530 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2010-01-12 22:25:55 +0530 |
commit | a04486dc997979a2d87fc013d30b6e71a3df4a64 (patch) | |
tree | fe3df7946d7fac940124f017fa0de7af335dba75 /activerecord/lib/active_record/relation | |
parent | b078f7fd3910b0b174bc951cc8c0d27536b09c16 (diff) | |
download | rails-a04486dc997979a2d87fc013d30b6e71a3df4a64.tar.gz rails-a04486dc997979a2d87fc013d30b6e71a3df4a64.tar.bz2 rails-a04486dc997979a2d87fc013d30b6e71a3df4a64.zip |
Delay building arel relation as long as possible for improved introspection
Diffstat (limited to 'activerecord/lib/active_record/relation')
4 files changed, 137 insertions, 139 deletions
diff --git a/activerecord/lib/active_record/relation/calculation_methods.rb b/activerecord/lib/active_record/relation/calculation_methods.rb index 5246c7bc5d..e6f62ee49a 100644 --- a/activerecord/lib/active_record/relation/calculation_methods.rb +++ b/activerecord/lib/active_record/relation/calculation_methods.rb @@ -25,7 +25,7 @@ module ActiveRecord operation = operation.to_s.downcase if operation == "count" - joins = @relation.joins(relation) + joins = arel.joins(arel) if joins.present? && joins =~ /LEFT OUTER/i distinct = true column_name = @klass.primary_key if column_name == :all @@ -40,7 +40,7 @@ module ActiveRecord distinct = options[:distinct] || distinct column_name = :all if column_name.blank? && operation == "count" - if @relation.send(:groupings).any? + if arel.send(:groupings).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 = @relation.send(:groupings).first.value + group_attr = arel.send(:groupings).first.value 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 @@ -165,12 +165,8 @@ module ActiveRecord column ? column.type_cast(value) : value end - def get_projection_name_from_chained_relations(relation = @relation) - if relation.respond_to?(:projections) && relation.projections.present? - relation.send(:select_clauses).join(', ') - elsif relation.respond_to?(:relation) - get_projection_name_from_chained_relations(relation.relation) - end + def get_projection_name_from_chained_relations + @select_values.join(", ") if @select_values.present? end end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index c3e5f27838..3668b0997f 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -93,15 +93,15 @@ module ActiveRecord result = where(primary_key.in(ids)).all expected_size = - if @relation.taken && ids.size > @relation.taken - @relation.taken + if arel.taken && ids.size > arel.taken + arel.taken else ids.size end # 11 ids with limit 3, offset 9 should give 2 results. - if @relation.skipped && (ids.size - @relation.skipped < expected_size) - expected_size = ids.size - @relation.skipped + if arel.skipped && (ids.size - arel.skipped < expected_size) + expected_size = ids.size - arel.skipped end if result.size == expected_size diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 5d7bf0b7bc..c07eca44e3 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1,68 +1,42 @@ module ActiveRecord module QueryMethods - - def preload(*associations) - spawn.tap {|r| r.preload_associations += Array.wrap(associations) } - end - - def includes(*associations) - spawn.tap {|r| r.includes_associations += Array.wrap(associations) } - end - - def eager_load(*associations) - spawn.tap {|r| r.eager_load_associations += Array.wrap(associations) } - end - - def readonly(status = true) - spawn.tap {|r| r.readonly = status } - end - - def create_with(attributes = {}) - spawn.tap {|r| r.create_with_attributes = attributes } - end - - def select(selects) - if selects.present? - relation = spawn(@relation.project(selects)) - relation.readonly = @relation.joins(relation).present? ? false : @readonly - relation - else - spawn + extend ActiveSupport::Concern + + included do + (ActiveRecord::Relation::ASSOCIATION_METHODS + ActiveRecord::Relation::MULTI_VALUE_METHODS).each do |query_method| + attr_accessor :"#{query_method}_values" + + class_eval <<-CEVAL + 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 + end + end + CEVAL end - end - - def from(from) - from.present? ? spawn(@relation.from(from)) : spawn - end - def having(*args) - return spawn if args.blank? + ActiveRecord::Relation::SINGLE_VALUE_METHODS.each do |query_method| + attr_accessor :"#{query_method}_value" - if [String, Hash, Array].include?(args.first.class) - havings = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) - else - havings = args.first + class_eval <<-CEVAL + def #{query_method}(value = true) + spawn.tap do |new_relation| + new_relation.#{query_method}_value = value + end + end + CEVAL end - - spawn(@relation.having(havings)) - end - - def group(groups) - groups.present? ? spawn(@relation.group(groups)) : spawn - end - - def order(orders) - orders.present? ? spawn(@relation.order(orders)) : spawn end def lock(locks = true) + relation = spawn case locks - when String - spawn(@relation.lock(locks)) - when TrueClass, NilClass - spawn(@relation.lock) + when String, TrueClass, NilClass + spawn.tap {|new_relation| new_relation.lock_value = locks || true } else - spawn + spawn.tap {|new_relation| new_relation.lock_value = false } end end @@ -70,7 +44,7 @@ module ActiveRecord relation = spawn relation.instance_variable_set(:@orders, nil) - order_clause = @relation.send(:order_clauses).join(', ') + order_clause = arel.send(:order_clauses).join(', ') if order_clause.present? relation.order(reverse_sql_order(order_clause)) else @@ -78,39 +52,74 @@ module ActiveRecord end end - def limit(limits) - limits.present? ? spawn(@relation.take(limits)) : spawn + def arel + @arel ||= build_arel end - def offset(offsets) - offsets.present? ? spawn(@relation.skip(offsets)) : spawn - end + def build_arel + arel = table - def on(join) - spawn(@relation.on(join)) - end + @joins_values.each do |j| + next if j.blank? - def joins(join, join_type = nil) - return spawn if join.blank? + @implicit_readonly = true - join_relation = case join - when String - @relation.join(join) - when Hash, Array, Symbol - if @klass.send(:array_of_strings?, join) - @relation.join(join.join(' ')) + case j + when Relation::JoinOperation + arel = arel.join(j.relation, j.join_class).on(j.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)) + end else - @relation.join(@klass.send(:build_association_joins, join)) + arel = arel.join(j) end - else - @relation.join(join, join_type) end - spawn(join_relation).tap { |r| r.readonly = true } + @where_values.each do |where| + if conditions = build_where(where) + arel = conditions.is_a?(String) ? arel.where(conditions) : arel.where(*conditions) + end + end + + @having_values.each do |where| + if conditions = build_where(where) + arel = conditions.is_a?(String) ? arel.having(conditions) : arel.having(*conditions) + end + end + + arel = arel.take(@limit_value) if @limit_value.present? + arel = arel.skip(@offset_value) if @offset_value.present? + + @group_values.each do |g| + arel = arel.group(g) if g.present? + end + + @order_values.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? + end + + arel = arel.from(@from_value) if @from_value.present? + + case @lock_value + when TrueClass + arel = arel.lock + when String + arel = arel.lock(@lock_value) + end + + arel end - def where(*args) - return spawn if args.blank? + def build_where(*args) + return if args.blank? builder = PredicateBuilder.new(Arel::Sql::Engine.new(@klass)) @@ -124,7 +133,7 @@ module ActiveRecord args.first end - conditions.is_a?(String) ? spawn(@relation.where(conditions)) : spawn(@relation.where(*conditions)) + conditions end private diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 4ecee8c634..66eae69d92 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -1,49 +1,51 @@ module ActiveRecord module SpawnMethods - def spawn(relation = @relation) - relation = Relation.new(@klass, relation) - relation.readonly = @readonly - relation.preload_associations = @preload_associations - relation.eager_load_associations = @eager_load_associations - relation.includes_associations = @includes_associations - relation.create_with_attributes = @create_with_attributes - relation.table = table + def spawn(arel_table = self.table) + relation = Relation.new(@klass, arel_table) + + (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |query_method| + relation.send(:"#{query_method}_values=", send(:"#{query_method}_values")) + end + + Relation::SINGLE_VALUE_METHODS.each do |query_method| + relation.send(:"#{query_method}_value=", send(:"#{query_method}_value")) + end + relation end def merge(r) raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass - merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations).includes(r.includes_associations) - merged_relation.readonly = r.readonly - - [self.relation, r.relation].each do |arel| - merged_relation = merged_relation. - joins(arel.joins(arel)). - group(arel.groupings). - limit(arel.taken). - offset(arel.skipped). - select(arel.send(:select_clauses)). - from(arel.sources). - having(arel.havings). - lock(arel.locked) - end + 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.lock_value = r.lock_value unless merged_relation.lock_value + + 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.send(:order_clause) - merged_order = relation_order.present? ? relation_order : order_clause - merged_relation = merged_relation.order(merged_order) + relation_order = r.order_values + merged_order = relation_order.present? ? relation_order : order_values + merged_relation.order_values = merged_order - merged_relation.create_with_attributes = @create_with_attributes + merged_relation.create_with_value = @create_with_value - if @create_with_attributes && r.create_with_attributes - merged_relation.create_with_attributes = @create_with_attributes.merge(r.create_with_attributes) + if @create_with_value && r.create_with_value + merged_relation.create_with_value = @create_with_value.merge(r.create_with_value) else - merged_relation.create_with_attributes = r.create_with_attributes || @create_with_attributes + merged_relation.create_with_value = r.create_with_value || @create_with_value end - merged_wheres = @relation.wheres + merged_wheres = @where_values - r.wheres.each do |w| + r.where_values.each do |w| if w.is_a?(Arel::Predicates::Equality) merged_wheres = merged_wheres.reject {|p| p.is_a?(Arel::Predicates::Equality) && p.operand1.name == w.operand1.name } end @@ -51,32 +53,23 @@ module ActiveRecord merged_wheres << w end - merged_relation.where(*merged_wheres) + merged_relation.where_values = merged_wheres + + merged_relation end alias :& :merge def except(*skips) result = Relation.new(@klass, table) - result.table = table - [:eager_load, :preload, :includes].each do |load_method| - result = result.send(load_method, send(:"#{load_method}_associations")) + (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |method| + result.send(:"#{method}_values=", send(:"#{method}_values")) unless skips.include?(method) end - result.readonly = self.readonly unless skips.include?(:readonly) - result.create_with_attributes = @create_with_attributes unless skips.include?(:create_with) - - result = result.joins(@relation.joins(@relation)) unless skips.include?(:joins) - result = result.group(@relation.groupings) unless skips.include?(:group) - result = result.limit(@relation.taken) unless skips.include?(:limit) - result = result.offset(@relation.skipped) unless skips.include?(:offset) - result = result.select(@relation.send(:select_clauses)) unless skips.include?(:select) - result = result.from(@relation.sources) unless skips.include?(:from) - result = result.order(order_clause) unless skips.include?(:order) - result = result.where(*@relation.wheres) unless skips.include?(:where) - result = result.having(*@relation.havings) unless skips.include?(:having) - result = result.lock(@relation.locked) unless skips.include?(:lock) + Relation::SINGLE_VALUE_METHODS.each do |method| + result.send(:"#{method}_value=", send(:"#{method}_value")) unless skips.include?(method) + end result end |