aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/relation/query_methods.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/relation/query_methods.rb')
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb306
1 files changed, 262 insertions, 44 deletions
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index a89d0f3ebf..8e6254f918 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1,5 +1,4 @@
require 'active_support/core_ext/array/wrap'
-require 'active_support/core_ext/object/blank'
module ActiveRecord
module QueryMethods
@@ -7,42 +6,67 @@ module ActiveRecord
Relation::MULTI_VALUE_METHODS.each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}_values # def select_values
- @values[:#{name}] || [] # @values[:select] || []
- end # end
- #
- def #{name}_values=(values) # def select_values=(values)
- @values[:#{name}] = values # @values[:select] = values
- end # end
+ def #{name}_values # def select_values
+ @values[:#{name}] || [] # @values[:select] || []
+ end # end
+ #
+ def #{name}_values=(values) # def select_values=(values)
+ raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
+ @values[:#{name}] = values # @values[:select] = values
+ end # end
CODE
end
(Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}_value # def readonly_value
- @values[:#{name}] # @values[:readonly]
- end # end
- #
- def #{name}_value=(value) # def readonly_value=(value)
- @values[:#{name}] = value # @values[:readonly] = value
- end # end
+ def #{name}_value # def readonly_value
+ @values[:#{name}] # @values[:readonly]
+ end # end
CODE
end
- def create_with_value
- @values[:create_with] || {}
+ Relation::SINGLE_VALUE_METHODS.each do |name|
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}_value=(value) # def readonly_value=(value)
+ raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
+ @values[:#{name}] = value # @values[:readonly] = value
+ end # end
+ CODE
end
- def create_with_value=(value)
- @values[:create_with] = value
+ def create_with_value # :nodoc:
+ @values[:create_with] || {}
end
alias extensions extending_values
+ # Specify relationships to be included in the result set. For
+ # example:
+ #
+ # users = User.includes(:address)
+ # users.each do |user|
+ # user.address.city
+ # end
+ #
+ # allows you to access the +address+ attribute of the +User+ model without
+ # firing an additional query. This will often result in a
+ # performance improvement over a simple +join+.
+ #
+ # === conditions
+ #
+ # If you want to add conditions to your included models you'll have
+ # to explicitly reference them. For example:
+ #
+ # User.includes(:posts).where('posts.name = ?', 'example')
+ #
+ # Will throw an error, but this will work:
+ #
+ # User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
def includes(*args)
args.empty? ? self : spawn.includes!(*args)
end
+ # Like #includes, but modifies the relation in place.
def includes!(*args)
args.reject! {|a| a.blank? }
@@ -50,19 +74,31 @@ module ActiveRecord
self
end
+ # Forces eager loading by performing a LEFT OUTER JOIN on +args+:
+ #
+ # User.eager_load(:posts)
+ # => SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
+ # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
+ # "users"."id"
def eager_load(*args)
args.blank? ? self : spawn.eager_load!(*args)
end
+ # Like #eager_load, but modifies relation in place.
def eager_load!(*args)
self.eager_load_values += args
self
end
+ # Allows preloading of +args+, in the same way that +includes+ does:
+ #
+ # User.preload(:posts)
+ # => SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
def preload(*args)
args.blank? ? self : spawn.preload!(*args)
end
+ # Like #preload, but modifies relation in place.
def preload!(*args)
self.preload_values += args
self
@@ -71,8 +107,6 @@ module ActiveRecord
# Used to indicate that an association is referenced by an SQL string, and should
# therefore be JOINed in any query rather than loaded separately.
#
- # For example:
- #
# User.includes(:posts).where("posts.name = 'foo'")
# # => Doesn't JOIN the posts table, resulting in an error.
#
@@ -82,8 +116,11 @@ module ActiveRecord
args.blank? ? self : spawn.references!(*args)
end
+ # Like #references, but modifies relation in place.
def references!(*args)
- self.references_values = (references_values + args.flatten.map(&:to_s)).uniq
+ args.flatten!
+
+ self.references_values = (references_values + args.map!(&:to_s)).uniq
self
end
@@ -91,7 +128,7 @@ module ActiveRecord
#
# First: takes a block so it can be used just like Array#select.
#
- # Model.scoped.select { |m| m.field == value }
+ # Model.all.select { |m| m.field == value }
#
# This will build an array of objects from the database for the scope,
# converting them into an array and iterating through them using Array#select.
@@ -124,33 +161,59 @@ module ActiveRecord
end
end
+ # Like #select, but modifies relation in place.
def select!(value)
self.select_values += Array.wrap(value)
self
end
+ # Allows to specify a group attribute:
+ #
+ # User.group(:name)
+ # => SELECT "users".* FROM "users" GROUP BY name
+ #
+ # Returns an array with distinct records based on the +group+ attribute:
+ #
+ # User.select([:id, :name])
+ # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">
+ #
+ # User.group(:name)
+ # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
def group(*args)
args.blank? ? self : spawn.group!(*args)
end
+ # Like #group, but modifies relation in place.
def group!(*args)
- self.group_values += args.flatten
+ args.flatten!
+
+ self.group_values += args
self
end
+ # Allows to specify an order attribute:
+ #
+ # User.order('name')
+ # => SELECT "users".* FROM "users" ORDER BY name
+ #
+ # User.order('name DESC')
+ # => SELECT "users".* FROM "users" ORDER BY name DESC
+ #
+ # User.order('name DESC, email')
+ # => SELECT "users".* FROM "users" ORDER BY name DESC, email
def order(*args)
args.blank? ? self : spawn.order!(*args)
end
+ # Like #order, but modifies relation in place.
def order!(*args)
- args = args.flatten
+ args.flatten!
references = args.reject { |arg| Arel::Node === arg }
- .map { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }
- .compact
+ references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
references!(references) if references.any?
- self.order_values += args
+ self.order_values = args + self.order_values
self
end
@@ -162,22 +225,29 @@ module ActiveRecord
#
# User.order('email DESC').reorder('id ASC').order('name ASC')
#
- # generates a query with 'ORDER BY id ASC, name ASC'.
- #
+ # generates a query with 'ORDER BY name ASC, id ASC'.
def reorder(*args)
args.blank? ? self : spawn.reorder!(*args)
end
+ # Like #reorder, but modifies relation in place.
def reorder!(*args)
+ args.flatten!
+
self.reordering_value = true
- self.order_values = args.flatten
+ self.order_values = args
self
end
+ # Performs a joins on +args+:
+ #
+ # User.joins(:posts)
+ # => SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
def joins(*args)
args.compact.blank? ? self : spawn.joins!(*args)
end
+ # Like #joins, but modifies relation in place.
def joins!(*args)
args.flatten!
@@ -194,10 +264,102 @@ module ActiveRecord
self
end
+ # Returns a new relation, which is the result of filtering the current relation
+ # according to the conditions in the arguments.
+ #
+ # #where accepts conditions in one of several formats. In the examples below, the resulting
+ # SQL is given as an illustration; the actual query generated may be different depending
+ # on the database adapter.
+ #
+ # === string
+ #
+ # A single string, without additional arguments, is passed to the query
+ # constructor as a SQL fragment, and used in the where clause of the query.
+ #
+ # Client.where("orders_count = '2'")
+ # # SELECT * from clients where orders_count = '2';
+ #
+ # Note that building your own string from user input may expose your application
+ # to injection attacks if not done properly. As an alternative, it is recommended
+ # to use one of the following methods.
+ #
+ # === array
+ #
+ # If an array is passed, then the first element of the array is treated as a template, and
+ # the remaining elements are inserted into the template to generate the condition.
+ # Active Record takes care of building the query to avoid injection attacks, and will
+ # convert from the ruby type to the database type where needed. Elements are inserted
+ # into the string in the order in which they appear.
+ #
+ # User.where(["name = ? and email = ?", "Joe", "joe@example.com"])
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # Alternatively, you can use named placeholders in the template, and pass a hash as the
+ # second element of the array. The names in the template are replaced with the corresponding
+ # values from the hash.
+ #
+ # User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # This can make for more readable code in complex queries.
+ #
+ # Lastly, you can use sprintf-style % escapes in the template. This works slightly differently
+ # than the previous methods; you are responsible for ensuring that the values in the template
+ # are properly quoted. The values are passed to the connector for quoting, but the caller
+ # is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting,
+ # the values are inserted using the same escapes as the Ruby core method <tt>Kernel::sprintf</tt>.
+ #
+ # User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # If #where is called with multiple arguments, these are treated as if they were passed as
+ # the elements of a single array.
+ #
+ # User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # When using strings to specify conditions, you can use any operator available from
+ # the database. While this provides the most flexibility, you can also unintentionally introduce
+ # dependencies on the underlying database. If your code is intended for general consumption,
+ # test with multiple database backends.
+ #
+ # === hash
+ #
+ # #where will also accept a hash condition, in which the keys are fields and the values
+ # are values to be searched for.
+ #
+ # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
+ #
+ # User.where({ name: "Joe", email: "joe@example.com" })
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
+ #
+ # User.where({ name: ["Alice", "Bob"]})
+ # # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
+ #
+ # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
+ # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
+ #
+ # === Joins
+ #
+ # If the relation is the result of a join, you may create a condition which uses any of the
+ # tables in the join. For string and array conditions, use the table name in the condition.
+ #
+ # User.joins(:posts).where("posts.created_at < ?", Time.now)
+ #
+ # For hash conditions, you can either use the table name in the key, or use a sub-hash.
+ #
+ # User.joins(:posts).where({ "posts.published" => true })
+ # User.joins(:posts).where({ :posts => { :published => true } })
+ #
+ # === empty condition
+ #
+ # If the condition returns true for blank?, then where is a no-op and returns the current relation.
def where(opts, *rest)
opts.blank? ? self : spawn.where!(opts, *rest)
end
+ # #where! is identical to #where, except that instead of returning a new relation, it adds
+ # the condition to the existing relation.
def where!(opts, *rest)
references!(PredicateBuilder.references(opts)) if Hash === opts
@@ -205,10 +367,15 @@ module ActiveRecord
self
end
+ # Allows to specify a HAVING clause. Note that you can't use HAVING
+ # without also specifying a GROUP clause.
+ #
+ # Order.having('SUM(price) > 30').group('user_id')
def having(opts, *rest)
opts.blank? ? self : spawn.having!(opts, *rest)
end
+ # Like #having, but modifies relation in place.
def having!(opts, *rest)
references!(PredicateBuilder.references(opts)) if Hash === opts
@@ -216,28 +383,45 @@ module ActiveRecord
self
end
+ # Specifies a limit for the number of records to retrieve.
+ #
+ # User.limit(10) # generated SQL has 'LIMIT 10'
+ #
+ # User.limit(10).limit(20) # generated SQL has 'LIMIT 20'
def limit(value)
spawn.limit!(value)
end
+ # Like #limit, but modifies relation in place.
def limit!(value)
self.limit_value = value
self
end
+ # Specifies the number of rows to skip before returning rows.
+ #
+ # User.offset(10) # generated SQL has "OFFSET 10"
+ #
+ # Should be used with order.
+ #
+ # User.offset(10).order("name ASC")
def offset(value)
spawn.offset!(value)
end
+ # Like #offset, but modifies relation in place.
def offset!(value)
self.offset_value = value
self
end
+ # Specifies locking settings (default to +true+). For more information
+ # on locking, please see +ActiveRecord::Locking+.
def lock(locks = true)
spawn.lock!(locks)
end
+ # Like #lock, but modifies relation in place.
def lock!(locks = true)
case locks
when String, TrueClass, NilClass
@@ -250,11 +434,11 @@ module ActiveRecord
end
# Returns a chainable relation with zero records, specifically an
- # instance of the NullRelation class.
+ # instance of the <tt>ActiveRecord::NullRelation</tt> class.
#
- # The returned NullRelation inherits from Relation and implements the
- # Null Object pattern so it is an object with defined null behavior:
- # it always returns an empty array of records and does not query the database.
+ # The returned <tt>ActiveRecord::NullRelation</tt> inherits from Relation and implements the
+ # Null Object pattern. It is an object with defined null behavior and always returns an empty
+ # array of records without quering the database.
#
# Any subsequent condition chained to the returned relation will continue
# generating an empty relation and will not fire any query to the database.
@@ -279,22 +463,47 @@ module ActiveRecord
# end
#
def none
- scoped.extending(NullRelation)
+ extending(NullRelation)
end
+ # Sets readonly attributes for the returned relation. If value is
+ # true (default), attempting to update a record will result in an error.
+ #
+ # users = User.readonly
+ # users.first.save
+ # => ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord
def readonly(value = true)
spawn.readonly!(value)
end
+ # Like #readonly, but modifies relation in place.
def readonly!(value = true)
self.readonly_value = value
self
end
+ # Sets attributes to be used when creating new records from a
+ # relation object.
+ #
+ # users = User.where(name: 'Oscar')
+ # users.new.name # => 'Oscar'
+ #
+ # users = users.create_with(name: 'DHH')
+ # users.new.name # => 'DHH'
+ #
+ # You can pass +nil+ to +create_with+ to reset attributes:
+ #
+ # users = users.create_with(nil)
+ # users.new.name # => 'Oscar'
def create_with(value)
spawn.create_with!(value)
end
+ # Like #create_with but modifies the relation in place. Raises
+ # +ImmutableRelation+ if the relation has already been loaded.
+ #
+ # users = User.all.create_with!(name: 'Oscar')
+ # users.new.name # => 'Oscar'
def create_with!(value)
self.create_with_value = value ? create_with_value.merge(value) : {}
self
@@ -307,16 +516,17 @@ module ActiveRecord
#
# Can accept other relation objects. For example:
#
- # Topic.select('title').from(Topics.approved)
+ # Topic.select('title').from(Topic.approved)
# # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
#
- # Topics.select('a.title').from(Topics.approved, :a)
+ # Topic.select('a.title').from(Topic.approved, :a)
# # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
#
def from(value, subquery_name = nil)
spawn.from!(value, subquery_name)
end
+ # Like #from, but modifies relation in place.
def from!(value, subquery_name = nil)
self.from_value = [value, subquery_name]
self
@@ -336,6 +546,7 @@ module ActiveRecord
spawn.uniq!(value)
end
+ # Like #uniq, but modifies relation in place.
def uniq!(value = true)
self.uniq_value = value
self
@@ -354,16 +565,16 @@ module ActiveRecord
# end
# end
#
- # scope = Model.scoped.extending(Pagination)
+ # scope = Model.all.extending(Pagination)
# scope.page(params[:page])
#
# You can also pass a list of modules:
#
- # scope = Model.scoped.extending(Pagination, SomethingElse)
+ # scope = Model.all.extending(Pagination, SomethingElse)
#
# === Using a block
#
- # scope = Model.scoped.extending do
+ # scope = Model.all.extending do
# def page(number)
# # pagination code goes here
# end
@@ -372,7 +583,7 @@ module ActiveRecord
#
# You can also use a block and a module list:
#
- # scope = Model.scoped.extending(Pagination) do
+ # scope = Model.all.extending(Pagination) do
# def per_page(number)
# # pagination code goes here
# end
@@ -385,30 +596,37 @@ module ActiveRecord
end
end
+ # Like #extending, but modifies relation in place.
def extending!(*modules, &block)
modules << Module.new(&block) if block_given?
- self.extending_values = modules.flatten
+ self.extending_values += modules.flatten
extend(*extending_values) if extending_values.any?
self
end
+ # Reverse the existing order clause on the relation.
+ #
+ # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
def reverse_order
spawn.reverse_order!
end
+ # Like #reverse_order, but modifies relation in place.
def reverse_order!
self.reverse_order_value = !reverse_order_value
self
end
+ # Returns the Arel object associated with the relation.
def arel
@arel ||= with_default_scope.build_arel
end
+ # Like #arel, but ignores the default scope of the model.
def build_arel
- arel = table.from table
+ arel = Arel::SelectManager.new(table.engine, table)
build_joins(arel, joins_values) unless joins_values.empty?