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/batches.rb12
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb188
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb110
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb32
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb314
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb115
6 files changed, 450 insertions, 321 deletions
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index d7494ebb5a..bf5a60f458 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -1,7 +1,7 @@
require 'active_support/core_ext/object/blank'
module ActiveRecord
- module Batches # :nodoc:
+ module Batches
# Yields each record that was found by the find +options+. The find is
# performed by find_in_batches with a batch size of 1000 (or as
# specified by the <tt>:batch_size</tt> option).
@@ -39,7 +39,7 @@ module ActiveRecord
# ascending on the primary key ("id ASC") to make the batch ordering
# work. This also mean that this method only works with integer-based
# primary keys. You can't set the limit either, that's used to control
- # the the batch sizes.
+ # the batch sizes.
#
# Example:
#
@@ -50,7 +50,7 @@ module ActiveRecord
def find_in_batches(options = {})
relation = self
- if orders.present? || taken.present?
+ unless arel.orders.blank? && arel.taken.blank?
ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
end
@@ -65,7 +65,7 @@ module ActiveRecord
batch_size = options.delete(:batch_size) || 1000
relation = relation.except(:order).order(batch_order).limit(batch_size)
- records = relation.where(primary_key.gteq(start)).all
+ records = relation.where(table[primary_key].gteq(start)).all
while records.any?
yield records
@@ -73,7 +73,7 @@ module ActiveRecord
break if records.size < batch_size
if primary_key_offset = records.last.id
- records = relation.where(primary_key.gt(primary_key_offset)).to_a
+ records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
else
raise "Primary key not included in the custom select clause"
end
@@ -83,7 +83,7 @@ module ActiveRecord
private
def batch_order
- "#{@klass.table_name}.#{@klass.primary_key} ASC"
+ "#{table_name}.#{primary_key} ASC"
end
end
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index a679c444cf..c1842b1a96 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -6,32 +6,30 @@ module ActiveRecord
# Count operates using three different approaches.
#
# * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
- # * Count using column: By passing a column name to count, it will return a count of all the
- # rows for the model with supplied column present
+ # * Count using column: By passing a column name to count, it will return a count of all the
+ # rows for the model with supplied column present.
# * Count using options will find the row count matched by the options used.
#
# The third approach, count using options, accepts an option hash as the only parameter. The options are:
#
- # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
+ # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
# See conditions in the intro to ActiveRecord::Base.
- # * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
- # or named associations in the same form used for the <tt>:include</tt> option, which will
- # perform an INNER JOIN on the associated table(s).
- # If the value is a string, then the records will be returned read-only since they will have
- # attributes that do not correspond to the table's columns.
+ # * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id"
+ # (rarely needed) or named associations in the same form used for the <tt>:include</tt> option, which will
+ # perform an INNER JOIN on the associated table(s). If the value is a string, then the records
+ # will be returned read-only since they will have attributes that do not correspond to the table's columns.
# Pass <tt>:readonly => false</tt> to override.
- # * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs.
- # The symbols named refer to already defined associations. When using named associations, count
+ # * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs.
+ # The symbols named refer to already defined associations. When using named associations, count
# returns the number of DISTINCT items for the model you're counting.
# See eager loading under Associations.
# * <tt>:order</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
# * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
- # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example,
- # want to do a join but not
- # include the joined columns.
- # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as
+ # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example,
+ # want to do a join but not include the joined columns.
+ # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as
# SELECT COUNT(DISTINCT posts.id) ...
- # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an
+ # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an
# alternate table name (or even the name of a database view).
#
# Examples for counting all:
@@ -44,16 +42,16 @@ module ActiveRecord
# Person.count(:conditions => "age > 26")
#
# # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN.
- # Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job)
+ # Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job)
#
# # finds the number of rows matching the conditions and joins.
- # Person.count(:conditions => "age > 26 AND job.salary > 60000",
- # :joins => "LEFT JOIN jobs on jobs.person_id = person.id")
+ # Person.count(:conditions => "age > 26 AND job.salary > 60000",
+ # :joins => "LEFT JOIN jobs on jobs.person_id = person.id")
#
# Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
# Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
#
- # Note: <tt>Person.count(:all)</tt> will not work because it will use <tt>:all</tt> as the condition.
+ # Note: <tt>Person.count(:all)</tt> will not work because it will use <tt>:all</tt> as the condition.
# Use Person.count instead.
def count(column_name = nil, options = {})
column_name, options = nil, column_name if column_name.is_a?(Hash)
@@ -95,14 +93,14 @@ module ActiveRecord
calculate(:sum, column_name, options)
end
- # This calculates aggregate values in the given column. Methods for count, sum, average,
- # minimum, and maximum have been added as shortcuts. Options such as <tt>:conditions</tt>,
+ # This calculates aggregate values in the given column. Methods for count, sum, average,
+ # minimum, and maximum have been added as shortcuts. Options such as <tt>:conditions</tt>,
# <tt>:order</tt>, <tt>:group</tt>, <tt>:having</tt>, and <tt>:joins</tt> can be passed to customize the query.
#
# There are two basic forms of output:
- # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
+ # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
# for AVG, and the given column's type for everything else.
- # * Grouped values: This returns an ordered hash of the values and groups them by the
+ # * Grouped values: This returns an ordered hash of the values and groups them by the
# <tt>:group</tt> option. It takes either a column name, or the name of a belongs_to association.
#
# values = Person.maximum(:age, :group => 'last_name')
@@ -119,29 +117,29 @@ module ActiveRecord
# end
#
# Options:
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
+ # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
# See conditions in the intro to ActiveRecord::Base.
- # * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything,
+ # * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything,
# the purpose of this is to access fields on joined tables in your conditions, order, or group clauses.
- # * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id".
+ # * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id".
# (Rarely needed).
- # The records will be returned read-only since they will have attributes that do not correspond to the
+ # The records will be returned read-only since they will have attributes that do not correspond to the
# table's columns.
# * <tt>:order</tt> - An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
# * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
- # * <tt>:select</tt> - By default, this is * as in SELECT * FROM, but can be changed if you for example
+ # * <tt>:select</tt> - By default, this is * as in SELECT * FROM, but can be changed if you for example
# want to do a join, but not include the joined columns.
- # * <tt>:distinct</tt> - Set this to true to make this a distinct calculation, such as
+ # * <tt>:distinct</tt> - Set this to true to make this a distinct calculation, such as
# SELECT COUNT(DISTINCT posts.id) ...
#
# Examples:
# Person.calculate(:count, :all) # The same as Person.count
# Person.average(:age) # SELECT AVG(age) FROM people...
- # Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for
+ # Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for
# # everyone with a last name other than 'Drake'
#
# # Selects the minimum age for any family without any minors
- # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name)
+ # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name)
#
# Person.sum("2 * age")
def calculate(operation, column_name, options = {})
@@ -163,80 +161,112 @@ module ActiveRecord
def perform_calculation(operation, column_name, options = {})
operation = operation.to_s.downcase
+ distinct = nil
+
if operation == "count"
column_name ||= (select_for_count || :all)
- joins = arel.joins(arel)
- if joins.present? && joins =~ /LEFT OUTER/i
+ unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
distinct = true
- column_name = @klass.primary_key if column_name == :all
+ column_name = primary_key if column_name == :all
end
- distinct = nil if column_name.to_s =~ /\s*DISTINCT\s+/i
- distinct ||= options[:distinct]
- else
- distinct = nil
+ distinct = nil if column_name =~ /\s*DISTINCT\s+/i
end
distinct = options[:distinct] || distinct
- column_name = :all if column_name.blank? && operation == "count"
if @group_values.any?
- return execute_grouped_calculation(operation, column_name)
+ execute_grouped_calculation(operation, column_name, distinct)
else
- return execute_simple_calculation(operation, column_name, distinct)
+ execute_simple_calculation(operation, column_name, distinct)
end
end
- def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
- column = if @klass.column_names.include?(column_name.to_s)
- Arel::Attribute.new(@klass.unscoped, column_name)
+ def aggregate_column(column_name)
+ if @klass.column_names.include?(column_name.to_s)
+ Arel::Attribute.new(@klass.unscoped.table, column_name)
else
- Arel::SqlLiteral.new(column_name == :all ? "*" : column_name.to_s)
+ Arel.sql(column_name == :all ? "*" : column_name.to_s)
end
+ end
- # Postgresql doesn't like ORDER BY when there are no GROUP BY
- relation = except(:order).select(operation == 'count' ? column.count(distinct) : column.send(operation))
- type_cast_calculated_value(@klass.connection.select_value(relation.to_sql), column_for(column_name), operation)
+ def operation_over_aggregate_column(column, operation, distinct)
+ operation == 'count' ? column.count(distinct) : column.send(operation)
end
- def execute_grouped_calculation(operation, column_name) #:nodoc:
- 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
- group_alias = column_alias_for(group_field)
- group_column = column_for(group_field)
+ def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
+ column = aggregate_column(column_name)
- group = @klass.connection.adapter_name == 'FrontBase' ? group_alias : group_field
+ # Postgresql doesn't like ORDER BY when there are no GROUP BY
+ relation = except(:order)
+ select_value = operation_over_aggregate_column(column, operation, distinct)
+
+ relation.select_values = [select_value]
- aggregate_alias = column_alias_for(operation, column_name)
+ query_builder = relation.arel
- select_statement = if operation == 'count' && column_name == :all
- "COUNT(*) AS count_all"
+ if operation == "count"
+ limit = relation.limit_value
+ offset = relation.offset_value
+
+ unless limit && offset
+ query_builder.limit = nil
+ query_builder.offset = nil
+ end
+ end
+
+ type_cast_calculated_value(@klass.connection.select_value(query_builder.to_sql), column_for(column_name), operation)
+ end
+
+ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
+ group_attr = @group_values
+ association = @klass.reflect_on_association(group_attr.first.to_sym)
+ associated = group_attr.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
+ group_fields = Array(associated ? association.foreign_key : group_attr)
+ group_aliases = group_fields.map { |field| column_alias_for(field) }
+ group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
+ [aliaz, column_for(field)]
+ }
+
+ group = @klass.connection.adapter_name == 'FrontBase' ? group_aliases : group_fields
+
+ if operation == 'count' && column_name == :all
+ aggregate_alias = 'count_all'
else
- Arel::Attribute.new(@klass.unscoped, column_name).send(operation).as(aggregate_alias).to_sql
+ aggregate_alias = column_alias_for(operation, column_name)
end
- select_statement << ", #{group_field} AS #{group_alias}"
+ select_values = [
+ operation_over_aggregate_column(
+ aggregate_column(column_name),
+ operation,
+ distinct).as(aggregate_alias)
+ ]
+
+ select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
+ "#{field} AS #{aliaz}"
+ }
- relation = except(:group).select(select_statement).group(group)
+ relation = except(:group).group(group.join(','))
+ relation.select_values = select_values
calculated_data = @klass.connection.select_all(relation.to_sql)
if association
- key_ids = calculated_data.collect { |row| row[group_alias] }
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
key_records = association.klass.base_class.find(key_ids)
- key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
+ key_records = Hash[key_records.map { |r| [r.id, r] }]
end
- calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
- key = type_cast_calculated_value(row[group_alias], group_column)
- key = key_records[key] if associated
- value = row[aggregate_alias]
- all[key] = type_cast_calculated_value(value, column_for(column_name), operation)
- all
- end
+ ActiveSupport::OrderedHash[calculated_data.map do |row|
+ key = group_columns.map { |aliaz, column|
+ type_cast_calculated_value(row[aliaz], column)
+ }
+ key = key.first if key.size == 1
+ key = key_records[key] if associated
+ [key, type_cast_calculated_value(row[aggregate_alias], column_for(column_name), operation)]
+ end]
end
# Converts the given keys to the value that the database adapter returns as
@@ -264,15 +294,11 @@ module ActiveRecord
end
def type_cast_calculated_value(value, column, operation = nil)
- if value.is_a?(String) || value.nil?
- case operation
- when 'count' then value.to_i
- when 'sum' then type_cast_using_column(value || '0', column)
- when 'average' then value.try(:to_d)
- else type_cast_using_column(value, column)
- end
- else
- value
+ case operation
+ when 'count' then value.to_i
+ when 'sum' then type_cast_using_column(value || '0', column)
+ when 'average' then value.respond_to?(:to_d) ? value.to_d : value
+ else type_cast_using_column(value, column)
end
end
@@ -282,7 +308,7 @@ module ActiveRecord
def select_for_count
if @select_values.present?
- select = @select_values.join(", ")
+ select = @select_values.join(", ")
select if select !~ /(,|\*)/
end
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index b34c11973b..25e23a9d55 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -19,29 +19,29 @@ module ActiveRecord
#
# All approaches accept an options hash as their last parameter.
#
- # ==== Parameters
+ # ==== Options
#
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>[ "user_name = ?", username ]</tt>,
+ # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>["user_name = ?", username]</tt>,
# or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
# * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
# * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
- # * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a
+ # * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a
# <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
# * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
- # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5,
+ # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5,
# it would skip rows 0 through 4.
# * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed),
- # named associations in the same form used for the <tt>:include</tt> option, which will perform an
+ # named associations in the same form used for the <tt>:include</tt> option, which will perform an
# <tt>INNER JOIN</tt> on the associated table(s),
# or an array containing a mixture of both strings and named associations.
- # If the value is a string, then the records will be returned read-only since they will
+ # If the value is a string, then the records will be returned read-only since they will
# have attributes that do not correspond to the table's columns.
# Pass <tt>:readonly => false</tt> to override.
# * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
# to already defined associations. See eager loading under Associations.
- # * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you,
+ # * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you,
# for example, want to do a join but not include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
- # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed
+ # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed
# to an alternate table name (or even the name of a database view).
# * <tt>:readonly</tt> - Mark the returned records read-only so they cannot be saved or updated.
# * <tt>:lock</tt> - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
@@ -54,7 +54,7 @@ module ActiveRecord
# Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
# Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
# Person.find([1]) # returns an array for the object with ID = 1
- # Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
+ # Person.where("administrator = 1").order("created_on DESC").find(1)
#
# Note that returned records may not be in the same order as the ids you
# provide since database rows are unordered. Give an explicit <tt>:order</tt>
@@ -63,23 +63,23 @@ module ActiveRecord
# ==== Examples
#
# # find first
- # Person.find(:first) # returns the first object fetched by SELECT * FROM people
- # Person.find(:first, :conditions => [ "user_name = ?", user_name])
- # Person.find(:first, :conditions => [ "user_name = :u", { :u => user_name }])
- # Person.find(:first, :order => "created_on DESC", :offset => 5)
+ # Person.first # returns the first object fetched by SELECT * FROM people
+ # Person.where(["user_name = ?", user_name]).first
+ # Person.where(["user_name = :u", { :u => user_name }]).first
+ # Person.order("created_on DESC").offset(5).first
#
# # find last
- # Person.find(:last) # returns the last object fetched by SELECT * FROM people
- # Person.find(:last, :conditions => [ "user_name = ?", user_name])
- # Person.find(:last, :order => "created_on DESC", :offset => 5)
+ # Person.last # returns the last object fetched by SELECT * FROM people
+ # Person.where(["user_name = ?", user_name]).last
+ # Person.order("created_on DESC").offset(5).last
#
# # find all
- # Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
- # Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
- # Person.find(:all, :conditions => { :friends => ["Bob", "Steve", "Fred"] }
- # Person.find(:all, :offset => 10, :limit => 10)
- # Person.find(:all, :include => [ :account, :friends ])
- # Person.find(:all, :group => "category")
+ # Person.all # returns an array of objects for all the rows fetched by SELECT * FROM people
+ # Person.where(["category IN (?)", categories]).limit(50).all
+ # Person.where({ :friends => ["Bob", "Steve", "Fred"] }).all
+ # Person.offset(10).limit(10).all
+ # Person.includes([:account, :friends]).all
+ # Person.group("category").all
#
# Example for find with a lock: Imagine two concurrent transactions:
# each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
@@ -88,7 +88,7 @@ module ActiveRecord
# expected <tt>person.visits == 4</tt>.
#
# Person.transaction do
- # person = Person.find(1, :lock => true)
+ # person = Person.lock(true).find(1)
# person.visits += 1
# person.save!
# end
@@ -123,6 +123,11 @@ module ActiveRecord
end
end
+ # Same as #first! but raises RecordNotFound if no record is returned
+ def first!(*args)
+ self.first(*args) or raise RecordNotFound
+ end
+
# A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass in all the
# same arguments to this method as you can to <tt>find(:last)</tt>.
def last(*args)
@@ -137,6 +142,11 @@ module ActiveRecord
end
end
+ # Same as #last! but raises RecordNotFound if no record is returned
+ def last!(*args)
+ self.last(*args) or raise RecordNotFound
+ end
+
# A convenience wrapper for <tt>find(:all, *args)</tt>. You can pass in all the
# same arguments to this method as you can to <tt>find(:all)</tt>.
def all(*args)
@@ -171,22 +181,25 @@ module ActiveRecord
def exists?(id = nil)
id = id.id if ActiveRecord::Base === id
+ relation = select("1").limit(1)
+
case id
when Array, Hash
- where(id).exists?
+ relation = relation.where(id)
else
- relation = select(primary_key).limit(1)
- relation = relation.where(primary_key.eq(id)) if id
- relation.first ? true : false
+ relation = relation.where(table[primary_key].eq(id)) if id
end
+
+ relation.first ? true : false
end
protected
def find_with_associations
including = (@eager_load_values + @includes_values).uniq
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, nil)
- rows = construct_relation_for_association_find(join_dependency).to_a
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
+ relation = construct_relation_for_association_find(join_dependency)
+ rows = connection.select_all(relation.to_sql, 'SQL', relation.bind_values)
join_dependency.instantiate(rows)
rescue ThrowResult
[]
@@ -194,13 +207,13 @@ module ActiveRecord
def construct_relation_for_association_calculations
including = (@eager_load_values + @includes_values).uniq
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, arel.joins(arel))
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, arel.froms.first)
relation = except(:includes, :eager_load, :preload)
apply_join_dependency(relation, join_dependency)
end
def construct_relation_for_association_find(join_dependency)
- relation = except(:includes, :eager_load, :preload, :select).select(column_aliases(join_dependency))
+ relation = except(:includes, :eager_load, :preload, :select).select(join_dependency.columns)
apply_join_dependency(relation, join_dependency)
end
@@ -222,15 +235,15 @@ module ActiveRecord
end
def construct_limited_ids_condition(relation)
- orders = relation.order_values.join(", ")
- values = @klass.connection.distinct("#{@klass.connection.quote_table_name @klass.table_name}.#{@klass.primary_key}", orders)
+ orders = relation.order_values
+ values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders)
- ids_array = relation.select(values).collect {|row| row[@klass.primary_key]}
- ids_array.empty? ? raise(ThrowResult) : primary_key.in(ids_array)
+ ids_array = relation.select(values).collect {|row| row[primary_key]}
+ ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
end
def find_by_attributes(match, attributes, *args)
- conditions = attributes.inject({}) {|h, a| h[a] = args[attributes.index(a)]; h}
+ conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}]
result = where(conditions).send(match.finder)
if match.bang? && result.blank?
@@ -288,19 +301,24 @@ module ActiveRecord
def find_one(id)
id = id.id if ActiveRecord::Base === id
- record = where(primary_key.eq(id)).first
+ column = columns_hash[primary_key]
+
+ substitute = connection.substitute_for(column, @bind_values)
+ relation = where(table[primary_key].eq(substitute))
+ relation.bind_values = [[column, id]]
+ record = relation.first
unless record
- conditions = arel.send(:where_clauses).join(', ')
- conditions = " [WHERE #{conditions}]" if conditions.present?
- raise RecordNotFound, "Couldn't find #{@klass.name} with ID=#{id}#{conditions}"
+ conditions = arel.where_sql
+ conditions = " [#{conditions}]" if conditions
+ raise RecordNotFound, "Couldn't find #{@klass.name} with #{primary_key}=#{id}#{conditions}"
end
record
end
def find_some(ids)
- result = where(primary_key.in(ids)).all
+ result = where(table[primary_key].in(ids)).all
expected_size =
if @limit_value && ids.size > @limit_value
@@ -317,7 +335,7 @@ module ActiveRecord
if result.size == expected_size
result
else
- conditions = arel.send(:where_clauses).join(', ')
+ conditions = arel.wheres.map { |x| x.value }.join(', ')
conditions = " [WHERE #{conditions}]" if conditions.present?
error = "Couldn't find all #{@klass.name.pluralize} with IDs "
@@ -342,14 +360,8 @@ module ActiveRecord
end
end
- def column_aliases(join_dependency)
- join_dependency.joins.collect{|join| join.column_names_with_alias.collect{|column_name, aliased_name|
- "#{connection.quote_table_name join.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}"}}.flatten.join(", ")
- end
-
def using_limitable_reflections?(reflections)
- reflections.none?(&:collection?)
+ reflections.none? { |r| r.collection? }
end
-
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 5cea2328e8..982b3d7e9f 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -1,33 +1,38 @@
module ActiveRecord
- class PredicateBuilder
-
- def initialize(engine)
- @engine = engine
- end
-
- def build_from_hash(attributes, default_table)
+ class PredicateBuilder # :nodoc:
+ def self.build_from_hash(engine, attributes, default_table)
predicates = attributes.map do |column, value|
table = default_table
if value.is_a?(Hash)
- table = Arel::Table.new(column, :engine => @engine)
- build_from_hash(value, table)
+ table = Arel::Table.new(column, engine)
+ build_from_hash(engine, value, table)
else
column = column.to_s
if column.include?('.')
table_name, column = column.split('.', 2)
- table = Arel::Table.new(table_name, :engine => @engine)
+ table = Arel::Table.new(table_name, engine)
end
- attribute = table[column] || Arel::Attribute.new(table, column)
+ attribute = table[column.to_sym]
case value
- when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Relation
- values = value.to_a
+ when ActiveRecord::Relation
+ value.select_values = [value.klass.arel_table['id']] if value.select_values.empty?
+ attribute.in(value.arel.ast)
+ when Array, ActiveRecord::Associations::CollectionProxy
+ values = value.to_a.map { |x|
+ x.is_a?(ActiveRecord::Base) ? x.id : x
+ }
attribute.in(values)
when Range, Arel::Relation
attribute.in(value)
+ when ActiveRecord::Base
+ attribute.eq(value.id)
+ when Class
+ # FIXME: I think we need to deprecate this behavior
+ attribute.eq(value.name)
else
attribute.eq(value)
end
@@ -36,6 +41,5 @@ module ActiveRecord
predicates.flatten
end
-
end
end
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)
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 7712ad2569..128e0fbd86 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -3,71 +3,109 @@ require 'active_support/core_ext/object/blank'
module ActiveRecord
module SpawnMethods
def merge(r)
+ return self unless r
+ return to_a & r if r.is_a?(Array)
+
merged_relation = clone
- return merged_relation unless r
- ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - [:joins, :where]).each do |method|
+ Relation::ASSOCIATION_METHODS.each do |method|
value = r.send(:"#{method}_values")
- merged_relation.send(:"#{method}_values=", value) if value.present?
- end
-
- merged_relation = merged_relation.joins(r.joins_values)
- merged_wheres = @where_values
-
- 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 }
+ unless value.empty?
+ if method == :includes
+ merged_relation = merged_relation.includes(value)
+ else
+ merged_relation.send(:"#{method}_values=", value)
+ end
end
+ end
- merged_wheres += [w]
+ (Relation::MULTI_VALUE_METHODS - [:joins, :where]).each do |method|
+ value = r.send(:"#{method}_values")
+ merged_relation.send(:"#{method}_values=", merged_relation.send(:"#{method}_values") + value) if value.present?
+ end
+
+ merged_relation.joins_values += r.joins_values
+
+ merged_wheres = @where_values + r.where_values
+
+ unless @where_values.empty?
+ # Remove duplicates, last one wins.
+ seen = Hash.new { |h,table| h[table] = {} }
+ merged_wheres = merged_wheres.reverse.reject { |w|
+ nuke = false
+ if w.respond_to?(:operator) && w.operator == :==
+ name = w.left.name
+ table = w.left.relation.name
+ nuke = seen[table][name]
+ seen[table][name] = true
+ end
+ nuke
+ }.reverse
end
merged_relation.where_values = merged_wheres
- Relation::SINGLE_VALUE_METHODS.reject {|m| m == :lock}.each do |method|
- unless (value = r.send(:"#{method}_value")).nil?
- merged_relation.send(:"#{method}_value=", value)
- end
+ (Relation::SINGLE_VALUE_METHODS - [:lock, :create_with]).each do |method|
+ value = r.send(:"#{method}_value")
+ merged_relation.send(:"#{method}_value=", value) unless value.nil?
end
merged_relation.lock_value = r.lock_value unless merged_relation.lock_value
+ merged_relation = merged_relation.create_with(r.create_with_value) if r.create_with_value
+
# Apply scope extension modules
merged_relation.send :apply_modules, r.extensions
merged_relation
end
- alias :& :merge
-
+ # Removes from the query the condition(s) specified in +skips+.
+ #
+ # Example:
+ #
+ # Post.order('id asc').except(:order) # discards the order condition
+ # Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
+ #
def except(*skips)
result = self.class.new(@klass, table)
- (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |method|
- result.send(:"#{method}_values=", send(:"#{method}_values")) unless skips.include?(method)
+ ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - skips).each do |method|
+ result.send(:"#{method}_values=", send(:"#{method}_values"))
end
- Relation::SINGLE_VALUE_METHODS.each do |method|
- result.send(:"#{method}_value=", send(:"#{method}_value")) unless skips.include?(method)
+ (Relation::SINGLE_VALUE_METHODS - skips).each do |method|
+ result.send(:"#{method}_value=", send(:"#{method}_value"))
end
+ # Apply scope extension modules
+ result.send(:apply_modules, extensions)
+
result
end
+ # Removes any condition from the query other than the one(s) specified in +onlies+.
+ #
+ # Example:
+ #
+ # Post.order('id asc').only(:where) # discards the order condition
+ # Post.order('id asc').only(:where, :order) # uses the specified order
+ #
def only(*onlies)
result = self.class.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
+ ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) & onlies).each do |method|
+ result.send(:"#{method}_values=", send(:"#{method}_values"))
+ end
+
+ (Relation::SINGLE_VALUE_METHODS & onlies).each do |method|
+ result.send(:"#{method}_value=", send(:"#{method}_value"))
end
+ # Apply scope extension modules
+ result.send(:apply_modules, extensions)
+
result
end
@@ -79,19 +117,16 @@ module ActiveRecord
return relation unless options
options.assert_valid_keys(VALID_FIND_OPTIONS)
+ finders = options.dup
+ finders.delete_if { |key, value| value.nil? && key != :limit }
- [:joins, :select, :group, :having, :limit, :offset, :from, :lock, :readonly].each do |finder|
- relation = relation.send(finder, options[finder]) if options.has_key?(finder)
- end
-
- # Give precedence to newly-applied orders and groups to play nicely with with_scope
- [:group, :order].each do |finder|
- relation.send("#{finder}_values=", Array.wrap(options[finder]) + relation.send("#{finder}_values")) if options.has_key?(finder)
+ ([:joins, :select, :group, :order, :having, :limit, :offset, :from, :lock, :readonly] & finders.keys).each do |finder|
+ relation = relation.send(finder, finders[finder])
end
- relation = relation.where(options[:conditions]) if options.has_key?(:conditions)
- relation = relation.includes(options[:include]) if options.has_key?(:include)
- relation = relation.extending(options[:extend]) if options.has_key?(:extend)
+ relation = relation.where(finders[:conditions]) if options.has_key?(:conditions)
+ relation = relation.includes(finders[:include]) if options.has_key?(:include)
+ relation = relation.extending(finders[:extend]) if options.has_key?(:extend)
relation
end