aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/relation
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/relation')
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb8
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb6
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb249
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb2
4 files changed, 141 insertions, 124 deletions
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 7a0c9dc612..f39951e16c 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -87,8 +87,8 @@ module ActiveRecord
# person.visits += 1
# person.save!
# end
- def find(*args, &block)
- return to_a.find(&block) if block_given?
+ def find(*args)
+ return to_a.find { |*block_args| yield(*block_args) } if block_given?
options = args.extract_options!
@@ -259,8 +259,8 @@ module ActiveRecord
record
end
- def find_with_ids(*ids, &block)
- return to_a.find(&block) if block_given?
+ def find_with_ids(*ids)
+ return to_a.find { |*block_args| yield(*block_args) } if block_given?
expects_array = ids.first.kind_of?(Array)
return ids.first if expects_array && ids.first.empty?
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index d0efa2189d..5cea2328e8 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -20,15 +20,13 @@ module ActiveRecord
table = Arel::Table.new(table_name, :engine => @engine)
end
- unless attribute = table[column]
- raise StatementInvalid, "No attribute named `#{column}` exists for table `#{table.name}`"
- end
+ attribute = table[column] || Arel::Attribute.new(table, column)
case value
when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Relation
values = value.to_a
attribute.in(values)
- when Range
+ when Range, Arel::Relation
attribute.in(value)
else
attribute.eq(value)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 7a48a6596a..4692271266 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -5,79 +5,92 @@ module ActiveRecord
module QueryMethods
extend ActiveSupport::Concern
- included do
- (ActiveRecord::Relation::ASSOCIATION_METHODS + ActiveRecord::Relation::MULTI_VALUE_METHODS).each do |query_method|
- attr_accessor :"#{query_method}_values"
-
- next if [:where, :having, :select].include?(query_method)
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
- def #{query_method}(*args, &block)
- new_relation = clone
- new_relation.send(:apply_modules, Module.new(&block)) if block_given?
- value = Array.wrap(args.flatten).reject {|x| x.blank? }
- new_relation.#{query_method}_values += value if value.present?
- new_relation
- end
- CEVAL
- end
+ attr_accessor :includes_values, :eager_load_values, :preload_values,
+ :select_values, :group_values, :order_values, :joins_values, :where_values, :having_values,
+ :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, :from_value
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
- def select(*args, &block)
- if block_given?
- to_a.select(&block)
- else
- new_relation = clone
- value = Array.wrap(args.flatten).reject {|x| x.blank? }
- new_relation.select_values += value if value.present?
- new_relation
- end
- end
- CEVAL
-
- [:where, :having].each do |query_method|
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
- def #{query_method}(*args, &block)
- new_relation = clone
- new_relation.send(:apply_modules, Module.new(&block)) if block_given?
- value = build_where(*args)
- new_relation.#{query_method}_values += Array.wrap(value) if value.present?
- new_relation
- end
- CEVAL
- end
+ def includes(*args)
+ args.reject! { |a| a.blank? }
+ clone.tap { |r| r.includes_values += args if args.present? }
+ end
+
+ def eager_load(*args)
+ clone.tap { |r| r.eager_load_values += args if args.present? }
+ end
- ActiveRecord::Relation::SINGLE_VALUE_METHODS.each do |query_method|
- attr_accessor :"#{query_method}_value"
+ def preload(*args)
+ clone.tap { |r| r.preload_values += args if args.present? }
+ end
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
- def #{query_method}(value = true, &block)
- new_relation = clone
- new_relation.send(:apply_modules, Module.new(&block)) if block_given?
- new_relation.#{query_method}_value = value
- new_relation
- end
- CEVAL
+ def select(*args)
+ if block_given?
+ to_a.select { |*block_args| yield(*block_args) }
+ else
+ clone.tap { |r| r.select_values += args if args.present? }
end
end
- def extending(*modules)
- new_relation = clone
- new_relation.send :apply_modules, *modules
- new_relation
+ def group(*args)
+ clone.tap { |r| r.group_values += args if args.present? }
end
- def lock(locks = true, &block)
- relation = clone
- relation.send(:apply_modules, Module.new(&block)) if block_given?
+ def order(*args)
+ clone.tap { |r| r.order_values += args if args.present? }
+ end
+
+ def reorder(*args)
+ clone.tap { |r| r.order_values = args if args.present? }
+ end
+ def joins(*args)
+ args.flatten!
+ clone.tap { |r| r.joins_values += args if args.present? }
+ end
+
+ def where(*args)
+ value = build_where(*args)
+ clone.tap { |r| r.where_values += Array.wrap(value) if value.present? }
+ end
+
+ def having(*args)
+ value = build_where(*args)
+ clone.tap { |r| r.having_values += Array.wrap(value) if value.present? }
+ end
+
+ def limit(value = true)
+ clone.tap { |r| r.limit_value = value }
+ end
+
+ def offset(value = true)
+ clone.tap { |r| r.offset_value = value }
+ end
+
+ def lock(locks = true)
case locks
when String, TrueClass, NilClass
- clone.tap {|new_relation| new_relation.lock_value = locks || true }
+ clone.tap { |r| r.lock_value = locks || true }
else
- clone.tap {|new_relation| new_relation.lock_value = false }
+ clone.tap { |r| r.lock_value = false }
end
end
+ def readonly(value = true)
+ clone.tap { |r| r.readonly_value = value }
+ end
+
+ def create_with(value = true)
+ clone.tap { |r| r.create_with_value = value }
+ end
+
+ def from(value = true)
+ clone.tap { |r| r.from_value = value }
+ end
+
+ def extending(*modules, &block)
+ modules << Module.new(&block) if block_given?
+ clone.tap { |r| r.send(:apply_modules, *modules) }
+ end
+
def reverse_order
order_clause = arel.send(:order_clauses).join(', ')
relation = except(:order)
@@ -116,45 +129,7 @@ module ActiveRecord
def build_arel
arel = table
- 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)
- end
-
- stashed_association_joins = joins.select {|j| j.is_a?(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)}
-
- non_association_joins = (joins - association_joins - stashed_association_joins).reject {|j| j.blank?}
- custom_joins = custom_join_sql(*non_association_joins)
-
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)
-
- join_dependency.graft(*stashed_association_joins)
-
- @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
-
- to_join = []
-
- 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
- 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
- arel = arel.join(tj[0], tj[1]).on(*tj[2])
- end
- end
-
- arel = arel.join(custom_joins)
+ arel = build_joins(arel, @joins_values) if @joins_values.present?
@where_values.uniq.each do |where|
next if where.blank?
@@ -168,31 +143,18 @@ module ActiveRecord
end
end
- @having_values.uniq.each do |h|
- arel = h.is_a?(String) ? arel.having(h) : arel.having(*h)
- end
+ arel = arel.having(*@having_values.uniq.select{|h| h.present?}) if @having_values.present?
arel = arel.take(@limit_value) if @limit_value.present?
arel = arel.skip(@offset_value) if @offset_value.present?
- arel = arel.group(*@group_values.uniq.select{|g| g.present?})
-
- arel = arel.order(*@order_values.uniq.select{|o| o.present?}.map(&:to_s))
+ arel = arel.group(*@group_values.uniq.select{|g| g.present?}) if @group_values.present?
- selects = @select_values.uniq
+ arel = arel.order(*@order_values.uniq.select{|o| o.present?}) if @order_values.present?
- quoted_table_name = @klass.quoted_table_name
-
- if selects.present?
- selects.each do |s|
- @implicit_readonly = false
- arel = arel.project(s) if s.present?
- end
- else
- arel = arel.project(quoted_table_name + '.*')
- end
+ arel = build_select(arel, @select_values.uniq)
- arel = @from_value.present? ? arel.from(@from_value) : arel.from(quoted_table_name)
+ arel = arel.from(@from_value) if @from_value.present?
case @lock_value
when TrueClass
@@ -221,6 +183,63 @@ module ActiveRecord
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)
+ end
+
+ stashed_association_joins = joins.select {|j| j.is_a?(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)}
+
+ non_association_joins = (joins - association_joins - stashed_association_joins)
+ custom_joins = custom_join_sql(*non_association_joins)
+
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)
+
+ join_dependency.graft(*stashed_association_joins)
+
+ @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
+
+ to_join = []
+
+ 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
+ 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
+
+ relation.join(custom_joins)
+ end
+
+ def build_select(arel, selects)
+ if selects.present?
+ @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
+ else
+ arel.project(@klass.quoted_table_name + '.*')
+ end
+ end
+
def apply_modules(modules)
values = Array.wrap(modules)
@extensions += values if values.present?
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index bb1f138f5b..7712ad2569 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -6,7 +6,7 @@ module ActiveRecord
merged_relation = clone
return merged_relation unless r
- (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).reject {|m| [:joins, :where].include?(m)}.each do |method|
+ ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - [:joins, :where]).each do |method|
value = r.send(:"#{method}_values")
merged_relation.send(:"#{method}_values=", value) if value.present?
end