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.rb15
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb1
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb3
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb18
-rw-r--r--activerecord/lib/active_record/relation/merger.rb15
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb165
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb17
7 files changed, 167 insertions, 67 deletions
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index fb4388d4b2..4d14506965 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/object/blank'
module ActiveRecord
module Batches
@@ -9,8 +8,8 @@ module ActiveRecord
# In that case, batch processing methods allow you to work
# with the records in batches, thereby greatly reducing memory consumption.
#
- # The <tt>find_each</tt> method uses <tt>find_in_batches</tt> with a batch size of 1000 (or as
- # specified by the <tt>:batch_size</tt> option).
+ # The #find_each method uses #find_in_batches with a batch size of 1000 (or as
+ # specified by the +:batch_size+ option).
#
# Person.all.find_each do |person|
# person.do_awesome_stuff
@@ -20,7 +19,7 @@ module ActiveRecord
# person.party_all_night!
# end
#
- # You can also pass the <tt>:start</tt> option to specify
+ # You can also pass the +:start+ option to specify
# an offset to control the starting point.
def find_each(options = {})
find_in_batches(options) do |records|
@@ -29,14 +28,14 @@ module ActiveRecord
end
# Yields each batch of records that was found by the find +options+ as
- # an array. The size of each batch is set by the <tt>:batch_size</tt>
+ # an array. The size of each batch is set by the +:batch_size+
# option; the default is 1000.
#
# You can control the starting point for the batch processing by
- # supplying the <tt>:start</tt> option. This is especially useful if you
+ # supplying the +:start+ option. This is especially useful if you
# want multiple workers dealing with the same processing queue. You can
# make worker 1 handle all the records between id 0 and 10,000 and
- # worker 2 handle from 10,000 and beyond (by setting the <tt>:start</tt>
+ # worker 2 handle from 10,000 and beyond (by setting the +:start+
# option on that worker).
#
# It's not possible to set the order. That is automatically set to
@@ -67,7 +66,7 @@ module ActiveRecord
batch_size = options.delete(:batch_size) || 1000
relation = relation.reorder(batch_order).limit(batch_size)
- records = relation.where(table[primary_key].gteq(start)).all
+ records = relation.where(table[primary_key].gteq(start)).to_a
while records.any?
records_size = records.size
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index e40b958b54..d93e7c8997 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/try'
module ActiveRecord
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 64dda4f35a..ab8b36c8ab 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -1,7 +1,6 @@
-require 'active_support/core_ext/module/delegation'
module ActiveRecord
- module Delegation
+ module Delegation # :nodoc:
# Set up common delegations for performance (avoids method_missing)
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :to => :to_a
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 974cd326ef..84aaa39fed 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/hash/indifferent_access'
module ActiveRecord
@@ -133,19 +132,6 @@ module ActiveRecord
last or raise RecordNotFound
end
- # Runs the query on the database and returns records with the used query
- # methods.
- #
- # 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
- def all
- to_a
- end
-
# Returns +true+ if a record exists in the table that matches the +id+ or
# conditions given, or +false+ otherwise. The argument can take six forms:
#
@@ -285,7 +271,7 @@ module ActiveRecord
end
def find_some(ids)
- result = where(table[primary_key].in(ids)).all
+ result = where(table[primary_key].in(ids)).to_a
expected_size =
if limit_value && ids.size > limit_value
@@ -324,7 +310,7 @@ module ActiveRecord
@records.first
else
@first ||=
- if order_values.empty? && primary_key
+ if with_default_scope.order_values.empty? && primary_key
order(arel_table[primary_key].asc).limit(1).to_a.first
else
limit(1).to_a.first
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index 36f98c6480..e5b50673da 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -1,9 +1,8 @@
-require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/hash/keys'
module ActiveRecord
class Relation
- class HashMerger
+ class HashMerger # :nodoc:
attr_reader :relation, :hash
def initialize(relation, hash)
@@ -28,7 +27,7 @@ module ActiveRecord
end
end
- class Merger
+ class Merger # :nodoc:
attr_reader :relation, :values
def initialize(relation, other)
@@ -98,15 +97,13 @@ module ActiveRecord
merged_wheres = relation.where_values + values[:where]
unless relation.where_values.empty?
- # Remove duplicates, last one wins.
- seen = Hash.new { |h,table| h[table] = {} }
+ # Remove equalities with duplicated left-hand. Last one wins.
+ seen = {}
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
+ nuke = seen[w.left]
+ seen[w.left] = true
end
nuke
}.reverse
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 6f49548aab..f6bacf4822 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
@@ -35,16 +34,39 @@ module ActiveRecord
CODE
end
- def 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? }
@@ -52,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
@@ -82,6 +116,7 @@ module ActiveRecord
args.blank? ? self : spawn.references!(*args)
end
+ # Like #references, but modifies relation in place.
def references!(*args)
args.flatten!
@@ -93,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.
@@ -101,8 +136,8 @@ module ActiveRecord
# Second: Modifies the SELECT statement for the query so that only certain
# fields are retrieved:
#
- # >> Model.select(:field)
- # => [#<Model field:value>]
+ # Model.select(:field)
+ # # => [#<Model field:value>]
#
# Although in the above example it looks as though this method returns an
# array, it actually returns a relation object and can have other query
@@ -110,31 +145,46 @@ module ActiveRecord
#
# The argument to the method can also be an array of fields.
#
- # >> Model.select([:field, :other_field, :and_one_more])
- # => [#<Model field: "value", other_field: "value", and_one_more: "value">]
+ # Model.select(:field, :other_field, :and_one_more)
+ # # => [#<Model field: "value", other_field: "value", and_one_more: "value">]
#
# Accessing attributes of an object that do not have fields retrieved by a select
# will throw <tt>ActiveModel::MissingAttributeError</tt>:
#
- # >> Model.select(:field).first.other_field
- # => ActiveModel::MissingAttributeError: missing attribute: other_field
- def select(value = Proc.new)
+ # Model.select(:field).first.other_field
+ # # => ActiveModel::MissingAttributeError: missing attribute: other_field
+ def select(*fields)
if block_given?
- to_a.select { |*block_args| value.call(*block_args) }
+ to_a.select { |*block_args| yield(*block_args) }
else
- spawn.select!(value)
+ raise ArgumentError, 'Call this with at least one field' if fields.empty?
+ spawn.select!(*fields)
end
end
- def select!(value)
- self.select_values += Array.wrap(value)
+ # Like #select, but modifies relation in place.
+ def select!(*fields)
+ self.select_values += fields.flatten
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)
args.flatten!
@@ -142,10 +192,21 @@ module ActiveRecord
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.flatten!
@@ -153,7 +214,7 @@ module ActiveRecord
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
@@ -165,11 +226,12 @@ 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!
@@ -178,10 +240,15 @@ module ActiveRecord
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!
@@ -301,10 +368,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
@@ -321,6 +393,7 @@ module ActiveRecord
spawn.limit!(value)
end
+ # Like #limit, but modifies relation in place.
def limit!(value)
self.limit_value = value
self
@@ -337,15 +410,19 @@ module ActiveRecord
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
@@ -358,11 +435,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.
@@ -387,22 +464,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
@@ -425,6 +527,7 @@ module ActiveRecord
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
@@ -444,6 +547,7 @@ module ActiveRecord
spawn.uniq!(value)
end
+ # Like #uniq, but modifies relation in place.
def uniq!(value = true)
self.uniq_value = value
self
@@ -462,16 +566,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
@@ -480,7 +584,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
@@ -493,10 +597,11 @@ 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
@@ -509,17 +614,20 @@ module ActiveRecord
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?
@@ -581,7 +689,8 @@ module ActiveRecord
when String, Array
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
when Hash
- PredicateBuilder.build_from_hash(table.engine, opts, table)
+ attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
+ PredicateBuilder.build_from_hash(table.engine, attributes, table)
else
[opts]
end
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 80d087a9ea..5394c1b28b 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/slice'
require 'active_record/relation/merger'
@@ -24,6 +23,13 @@ module ActiveRecord
# # Returns the intersection of all published posts with the 5 most recently created posts.
# # (This is just an example. You'd probably want to do this with a single query!)
#
+ # Procs will be evaluated by merge:
+ #
+ # Post.where(published: true).merge(-> { joins(:comments) })
+ # # => Post.where(published: true).joins(:comments)
+ #
+ # This is mainly intended for sharing common conditions between multiple associations.
+ #
def merge(other)
if other.is_a?(Array)
to_a & other
@@ -34,9 +40,14 @@ module ActiveRecord
end
end
+ # Like #merge, but applies changes in place.
def merge!(other)
- klass = other.is_a?(Hash) ? Relation::HashMerger : Relation::Merger
- klass.new(self, other).merge
+ if !other.is_a?(Relation) && other.respond_to?(:to_proc)
+ instance_exec(&other)
+ else
+ klass = other.is_a?(Hash) ? Relation::HashMerger : Relation::Merger
+ klass.new(self, other).merge
+ end
end
# Removes from the query the condition(s) specified in +skips+.