aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md31
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb59
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb1
-rw-r--r--activerecord/lib/active_record/model_schema.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb92
-rw-r--r--activerecord/lib/active_record/relation/batches.rb9
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb118
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb155
-rw-r--r--activerecord/lib/active_record/relation/merger.rb112
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb286
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb115
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb3
-rw-r--r--activerecord/test/cases/associations/eager_test.rb123
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb8
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb2
-rw-r--r--activerecord/test/cases/base_test.rb8
-rw-r--r--activerecord/test/cases/calculations_test.rb10
-rw-r--r--activerecord/test/cases/connection_pool_test.rb24
-rw-r--r--activerecord/test/cases/finder_test.rb4
-rw-r--r--activerecord/test/cases/migration/references_index_test.rb99
-rw-r--r--activerecord/test/cases/migration_test.rb9
-rw-r--r--activerecord/test/cases/modules_test.rb2
-rw-r--r--activerecord/test/cases/relation_test.rb136
25 files changed, 839 insertions, 608 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 26f6093bc2..aa84dacf07 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,34 @@
## Rails 4.0.0 (unreleased) ##
+* Added an :index option to automatically create indexes for references
+ and belongs_to statements in migrations.
+
+ The `references` and `belongs_to` methods now support an `index`
+ option that receives either a boolean value or an options hash
+ that is identical to options available to the add_index method:
+
+ create_table :messages do |t|
+ t.references :person, :index => true
+ end
+
+ Is the same as:
+
+ create_table :messages do |t|
+ t.references :person
+ end
+ add_index :messages, :person_id
+
+ Generators have also been updated to use the new syntax.
+
+ [Joshua Wood]
+
+* Added bang methods for mutating `ActiveRecord::Relation` objects.
+ For example, while `foo.where(:bar)` will return a new object
+ leaving `foo` unchanged, `foo.where!(:bar)` will mutate the foo
+ object
+
+ *Jon Leighton*
+
* Added `#find_by` and `#find_by!` to mirror the functionality
provided by dynamic finders in a way that allows dynamic input more
easily:
@@ -210,7 +239,7 @@
* PostgreSQL hstore types are automatically deserialized from the database.
-## Rails 3.2.3 (unreleased) ##
+## Rails 3.2.3 (March 30, 2012) ##
* Added find_or_create_by_{attribute}! dynamic method. *Andrew White*
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 2972b7e13e..5a44d3a156 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -15,19 +15,20 @@ module ActiveRecord
def scope
scope = klass.unscoped
- scope = scope.extending(*Array(options[:extend]))
+
+ scope.extending!(*Array(options[:extend]))
# It's okay to just apply all these like this. The options will only be present if the
# association supports that option; this is enforced by the association builder.
- scope = scope.apply_finder_options(options.slice(
- :readonly, :include, :references, :order, :limit, :joins, :group, :having, :offset, :select))
+ scope.merge!(options.slice(
+ :readonly, :references, :order, :limit, :joins, :group, :having, :offset, :select, :uniq))
- if options[:through] && !options[:include]
- scope = scope.includes(source_options[:include])
+ if options[:include]
+ scope.includes! options[:include]
+ elsif options[:through]
+ scope.includes! source_options[:include]
end
- scope = scope.uniq if options[:uniq]
-
add_constraints(scope)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 561e48d52e..a609867898 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -2,6 +2,7 @@ require 'thread'
require 'monitor'
require 'set'
require 'active_support/core_ext/module/deprecation'
+require 'timeout'
module ActiveRecord
# Raised when a connection could not be obtained within the connection
@@ -11,9 +12,6 @@ module ActiveRecord
# Raised when a connection pool is full and another connection is requested
class PoolFullError < ConnectionNotEstablished
- def initialize size, timeout
- super("Connection pool of size #{size} and timeout #{timeout}s is full")
- end
end
module ConnectionAdapters
@@ -94,6 +92,21 @@ module ActiveRecord
attr_accessor :automatic_reconnect, :timeout
attr_reader :spec, :connections, :size, :reaper
+ class Latch # :nodoc:
+ def initialize
+ @mutex = Mutex.new
+ @cond = ConditionVariable.new
+ end
+
+ def release
+ @mutex.synchronize { @cond.broadcast }
+ end
+
+ def await
+ @mutex.synchronize { @cond.wait @mutex }
+ end
+ end
+
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
# object which describes database connection information (e.g. adapter,
# host name, username, password, etc), as well as the maximum size for
@@ -115,6 +128,7 @@ module ActiveRecord
# default max pool size to 5
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
+ @latch = Latch.new
@connections = []
@automatic_reconnect = true
end
@@ -139,8 +153,10 @@ module ActiveRecord
# #release_connection releases the connection-thread association
# and returns the connection to the pool.
def release_connection(with_id = current_connection_id)
- conn = @reserved_connections.delete(with_id)
- checkin conn if conn
+ synchronize do
+ conn = @reserved_connections.delete(with_id)
+ checkin conn if conn
+ end
end
# If a connection already exists yield it to the block. If no connection
@@ -205,23 +221,23 @@ module ActiveRecord
# Raises:
# - PoolFullError: no connection can be obtained from the pool.
def checkout
- # Checkout an available connection
- synchronize do
- # Try to find a connection that hasn't been leased, and lease it
- conn = connections.find { |c| c.lease }
-
- # If all connections were leased, and we have room to expand,
- # create a new connection and lease it.
- if !conn && connections.size < size
- conn = checkout_new_connection
- conn.lease
- end
+ loop do
+ # Checkout an available connection
+ synchronize do
+ # Try to find a connection that hasn't been leased, and lease it
+ conn = connections.find { |c| c.lease }
+
+ # If all connections were leased, and we have room to expand,
+ # create a new connection and lease it.
+ if !conn && connections.size < size
+ conn = checkout_new_connection
+ conn.lease
+ end
- if conn
- checkout_and_verify conn
- else
- raise PoolFullError.new(size, timeout)
+ return checkout_and_verify(conn) if conn
end
+
+ Timeout.timeout(@timeout, PoolFullError) { @latch.await }
end
end
@@ -238,6 +254,7 @@ module ActiveRecord
release conn
end
+ @latch.release
end
# Remove a connection from the connection pool. The connection will
@@ -250,6 +267,7 @@ module ActiveRecord
# from the reserved hash will be a little easier.
release conn
end
+ @latch.release
end
# Removes dead connections from the pool. A dead connection can occur
@@ -262,6 +280,7 @@ module ActiveRecord
remove conn if conn.in_use? && stale > conn.last_use && !conn.active?
end
end
+ @latch.release
end
private
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 7ee8f40631..3546873550 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -65,11 +65,12 @@ module ActiveRecord
class TableDefinition
# An array of ColumnDefinition objects, representing the column changes
# that have been defined.
- attr_accessor :columns
+ attr_accessor :columns, :indexes
def initialize(base)
@columns = []
@columns_hash = {}
+ @indexes = {}
@base = base
end
@@ -212,19 +213,22 @@ module ActiveRecord
#
# TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
# column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of
- # options, these will be used when creating the <tt>_type</tt> column. So what can be written like this:
+ # options, these will be used when creating the <tt>_type</tt> column. The <tt>:index</tt> option
+ # will also create an index, similar to calling <tt>add_index</tt>. So what can be written like this:
#
# create_table :taggings do |t|
# t.integer :tag_id, :tagger_id, :taggable_id
# t.string :tagger_type
# t.string :taggable_type, :default => 'Photo'
# end
+ # add_index :taggings, :tag_id, :name => 'index_taggings_on_tag_id'
+ # add_index :taggings, [:tagger_id, :tagger_type]
#
# Can also be written as follows using references:
#
# create_table :taggings do |t|
- # t.references :tag
- # t.references :tagger, :polymorphic => true
+ # t.references :tag, :index => { :name => 'index_taggings_on_tag_id' }
+ # t.references :tagger, :polymorphic => true, :index => true
# t.references :taggable, :polymorphic => { :default => 'Photo' }
# end
def column(name, type, options = {})
@@ -255,6 +259,14 @@ module ActiveRecord
end # end
EOV
end
+
+ # Adds index options to the indexes hash, keyed by column name
+ # This is primarily used to track indexes that need to be created after the table
+ # === Examples
+ # index(:account_id, :name => 'index_projects_on_account_id')
+ def index(column_name, options = {})
+ indexes[column_name] = options
+ end
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
# <tt>:updated_at</tt> to the table.
@@ -267,9 +279,11 @@ module ActiveRecord
def references(*args)
options = args.extract_options!
polymorphic = options.delete(:polymorphic)
+ index_options = options.delete(:index)
args.each do |col|
column("#{col}_id", :integer, options)
column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
+ index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
end
end
alias :belongs_to :references
@@ -435,9 +449,11 @@ module ActiveRecord
def references(*args)
options = args.extract_options!
polymorphic = options.delete(:polymorphic)
+ index_options = options.delete(:index)
args.each do |col|
@base.add_column(@table_name, "#{col}_id", :integer, options)
@base.add_column(@table_name, "#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
+ @base.add_index(@table_name, polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
end
end
alias :belongs_to :references
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 59ae3ef981..30a4f9aa35 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -171,6 +171,7 @@ module ActiveRecord
create_sql << td.to_sql
create_sql << ") #{options[:options]}"
execute create_sql
+ td.indexes.each_pair { |c,o| add_index table_name, c, o }
end
# Creates a new join table with the name created using the lexical order of the first two
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index c85d590ce1..7f38dda11e 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -172,8 +172,8 @@ module ActiveRecord
end
def reset_sequence_name #:nodoc:
- @sequence_name = connection.default_sequence_name(table_name, primary_key)
@explicit_sequence_name = false
+ @sequence_name = connection.default_sequence_name(table_name, primary_key)
end
# Sets the name of the sequence to use when generating ids to the given
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index b007b8c168..0dbaab306f 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -6,28 +6,30 @@ module ActiveRecord
# = Active Record Relation
class Relation
JoinOperation = Struct.new(:relation, :join_class, :on)
- ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
- MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind, :references]
- SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering, :reverse_order, :uniq]
+
+ MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
+ :order, :joins, :where, :having, :bind, :references,
+ :extending]
+
+ SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
+ :reverse_order, :uniq, :create_with]
+
+ VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
attr_reader :table, :klass, :loaded
- attr_accessor :extensions, :default_scoped
+ attr_accessor :default_scoped
alias :loaded? :loaded
alias :default_scoped? :default_scoped
- def initialize(klass, table)
- @klass, @table = klass, table
-
+ def initialize(klass, table, values = {})
+ @klass = klass
+ @table = table
+ @values = values
@implicit_readonly = nil
@loaded = false
@default_scoped = false
-
- SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
- (ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
- @extensions = []
- @create_with_value = {}
end
def insert(values)
@@ -78,7 +80,8 @@ module ActiveRecord
end
def initialize_copy(other)
- @bind_values = @bind_values.dup
+ @values = @values.dup
+ @values[:bind] = @values[:bind].dup if @values[:bind]
reset
end
@@ -168,17 +171,17 @@ module ActiveRecord
default_scoped = with_default_scope
if default_scoped.equal?(self)
- @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
+ @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values)
- preload = @preload_values
- preload += @includes_values unless eager_loading?
+ preload = preload_values
+ preload += includes_values unless eager_loading?
preload.each do |associations|
ActiveRecord::Associations::Preloader.new(@records, associations).run
end
# @readonly_value is true only if set explicitly. @implicit_readonly is true if there
# are JOINS and no explicit SELECT.
- readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value
+ readonly = readonly_value.nil? ? @implicit_readonly : readonly_value
@records.each { |record| record.readonly! } if readonly
else
@records = default_scoped.to_a
@@ -218,7 +221,7 @@ module ActiveRecord
if block_given?
to_a.many? { |*block_args| yield(*block_args) }
else
- @limit_value ? to_a.many? : size > 1
+ limit_value ? to_a.many? : size > 1
end
end
@@ -254,39 +257,26 @@ module ActiveRecord
# Customer.update_all :wants_email => true
#
# # Update all books with 'Rails' in their title
- # Book.update_all "author = 'David'", "title LIKE '%Rails%'"
- #
- # # Update all avatars migrated more recently than a week ago
- # Avatar.update_all ['migrated_at = ?', Time.now.utc], ['migrated_at > ?', 1.week.ago]
- #
- # # Update all books that match conditions, but limit it to 5 ordered by date
- # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5
- #
- # # Conditions from the current relation also works
# Book.where('title LIKE ?', '%Rails%').update_all(:author => 'David')
#
- # # The same idea applies to limit and order
+ # # Update all books that match conditions, but limit it to 5 ordered by date
# Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(:author => 'David')
- def update_all(updates, conditions = nil, options = {})
- if conditions || options.present?
- where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates)
- else
- stmt = Arel::UpdateManager.new(arel.engine)
-
- stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
- stmt.table(table)
- stmt.key = table[primary_key]
+ def update_all(updates)
+ stmt = Arel::UpdateManager.new(arel.engine)
- if joins_values.any?
- @klass.connection.join_to_update(stmt, arel)
- else
- stmt.take(arel.limit)
- stmt.order(*arel.orders)
- stmt.wheres = arel.constraints
- end
+ stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
+ stmt.table(table)
+ stmt.key = table[primary_key]
- @klass.connection.update stmt, 'SQL', bind_values
+ if joins_values.any?
+ @klass.connection.join_to_update(stmt, arel)
+ else
+ stmt.take(arel.limit)
+ stmt.order(*arel.orders)
+ stmt.wheres = arel.constraints
end
+
+ @klass.connection.update stmt, 'SQL', bind_values
end
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
@@ -454,7 +444,7 @@ module ActiveRecord
end
def to_sql
- @to_sql ||= klass.connection.to_sql(arel, @bind_values.dup)
+ @to_sql ||= klass.connection.to_sql(arel, bind_values.dup)
end
def where_values_hash
@@ -476,8 +466,8 @@ module ActiveRecord
def eager_loading?
@should_eager_load ||=
- @eager_load_values.any? ||
- @includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
+ eager_load_values.any? ||
+ includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
end
# Joins that are also marked for preloading. In which case we should just eager load them.
@@ -485,7 +475,7 @@ module ActiveRecord
# represent the same association, but that aren't matched by this. Also, we could have
# nested hashes which partially match, e.g. { :a => :b } & { :a => [:b, :c] }
def joined_includes_values
- @includes_values & @joins_values
+ includes_values & joins_values
end
def ==(other)
@@ -519,6 +509,10 @@ module ActiveRecord
to_a.blank?
end
+ def values
+ @values.dup
+ end
+
private
def references_eager_loaded_tables?
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 2fd89882ff..15f838a5ab 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -46,19 +46,14 @@ module ActiveRecord
# group.each { |person| person.party_all_night! }
# end
def find_in_batches(options = {})
+ options.assert_valid_keys(:start, :batch_size)
+
relation = self
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
- if (finder_options = options.except(:start, :batch_size)).present?
- raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present?
- raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?
-
- relation = apply_finder_options(finder_options)
- end
-
start = options.delete(:start).to_i
batch_size = options.delete(:batch_size) || 1000
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index f613014f23..db894c8aa9 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -3,56 +3,19 @@ require 'active_support/core_ext/object/try'
module ActiveRecord
module Calculations
- # Count operates using three different approaches.
+ # Count the records.
#
- # * 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 options will find the row count matched by the options used.
+ # Person.count
+ # # => the total count of all people
#
- # The third approach, count using options, accepts an option hash as the only parameter. The options are:
+ # Person.count(:age)
+ # # => returns the total count of all people whose age is present in database
#
- # * <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.
- # 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
- # 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
- # SELECT COUNT(DISTINCT posts.id) ...
- # * <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).
+ # Person.count(:all)
+ # # => performs a COUNT(*) (:all is an alias for '*')
#
- # Examples for counting all:
- # Person.count # returns the total count of all people
- #
- # Examples for counting by column:
- # Person.count(:age) # returns the total count of all people whose age is present in database
- #
- # Examples for count with options:
- # 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)
- #
- # # 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('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.
- # Use Person.count instead.
+ # Person.count(:age, distinct: true)
+ # # => counts the number of different age values
def count(column_name = nil, options = {})
column_name, options = nil, column_name if column_name.is_a?(Hash)
calculate(:count, column_name, options)
@@ -98,21 +61,22 @@ module ActiveRecord
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>,
- # <tt>:order</tt>, <tt>:group</tt>, <tt>:having</tt>, and <tt>:joins</tt> can be passed to customize the query.
+ # minimum, and maximum have been added as shortcuts.
#
# There are two basic forms of output:
+ #
# * 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
- # <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')
+ # * Grouped values: This returns an ordered hash of the values and groups them. It
+ # takes either a column name, or the name of a belongs_to association.
+ #
+ # values = Person.group('last_name').maximum(:age)
# puts values["Drake"]
# => 43
#
# drake = Family.find_by_last_name('Drake')
- # values = Person.maximum(:age, :group => :family) # Person belongs_to :family
+ # values = Person.group(:family).maximum(:age) # Person belongs_to :family
# puts values[drake]
# => 43
#
@@ -120,47 +84,25 @@ module ActiveRecord
# ...
# end
#
- # Options:
- # * <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,
- # 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".
- # (Rarely needed).
- # 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
- # 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) ...
- #
# 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
- # # 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.group(:last_name).having("min(age) > 17").minimum(:age)
#
# Person.sum("2 * age")
def calculate(operation, column_name, options = {})
- if options.except(:distinct).present?
- apply_finder_options(options.except(:distinct)).calculate(operation, column_name, :distinct => options[:distinct])
- else
- relation = with_default_scope
-
- if relation.equal?(self)
- if eager_loading? || (includes_values.present? && references_eager_loaded_tables?)
- construct_relation_for_association_calculations.calculate(operation, column_name, options)
- else
- perform_calculation(operation, column_name, options)
- end
+ relation = with_default_scope
+
+ if relation.equal?(self)
+ if eager_loading? || (includes_values.present? && references_eager_loaded_tables?)
+ construct_relation_for_association_calculations.calculate(operation, column_name, options)
else
- relation.calculate(operation, column_name, options)
+ perform_calculation(operation, column_name, options)
end
+ else
+ relation.calculate(operation, column_name, options)
end
rescue ThrowResult
0
@@ -216,7 +158,7 @@ module ActiveRecord
distinct = nil if column_name =~ /\s*DISTINCT\s+/i
end
- if @group_values.any?
+ if group_values.any?
execute_grouped_calculation(operation, column_name, distinct)
else
execute_simple_calculation(operation, column_name, distinct)
@@ -259,7 +201,7 @@ module ActiveRecord
end
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
- group_attr = @group_values
+ 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)
@@ -282,7 +224,7 @@ module ActiveRecord
operation,
distinct).as(aggregate_alias)
]
- select_values += @select_values unless @having_values.empty?
+ select_values += select_values unless having_values.empty?
select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
"#{field} AS #{aliaz}"
@@ -347,8 +289,8 @@ module ActiveRecord
end
def select_for_count
- if @select_values.present?
- select = @select_values.join(", ")
+ if select_values.present?
+ 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 74f8e30404..416b55f5c5 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -3,83 +3,24 @@ require 'active_support/core_ext/hash/indifferent_access'
module ActiveRecord
module FinderMethods
- # Find operates with four different retrieval approaches:
- #
- # * Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
- # If no record can be found for all of the listed ids, then RecordNotFound will be raised.
- # * Find first - This will return the first record matched by the options used. These options can either be specific
- # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
- # <tt>Model.find(:first, *args)</tt> or its shortcut <tt>Model.first(*args)</tt>.
- # * Find last - This will return the last record matched by the options used. These options can either be specific
- # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
- # <tt>Model.find(:last, *args)</tt> or its shortcut <tt>Model.last(*args)</tt>.
- # * Find all - This will return all the records matched by the options used.
- # If no records are found, an empty array is returned. Use
- # <tt>Model.find(:all, *args)</tt> or its shortcut <tt>Model.all(*args)</tt>.
- #
- # All approaches accept an options hash as their last parameter.
- #
- # ==== Options
- #
- # * <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>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,
- # 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
- # <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
- # 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,
- # 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
- # 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".
- # <tt>:lock => true</tt> gives connection's default exclusive lock, usually "FOR UPDATE".
+ # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
+ # If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key
+ # is an integer, find by id coerces its arguments using +to_i+.
#
# ==== Examples
#
- # # find by id
# Person.find(1) # returns the object for ID = 1
+ # Person.find("1") # returns the object for ID = 1
# 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.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>
+ # provide since database rows are unordered. Give an explicit <tt>order</tt>
# to ensure the results are sorted.
#
- # ==== Examples
- #
- # # find first
- # 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.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.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
+ # ==== Find with lock
#
# 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
@@ -93,19 +34,10 @@ module ActiveRecord
# person.save!
# end
def find(*args)
- return to_a.find { |*block_args| yield(*block_args) } if block_given?
-
- options = args.extract_options!
-
- if options.present?
- apply_finder_options(options).find(*args)
+ if block_given?
+ to_a.find { |*block_args| yield(*block_args) }
else
- case args.first
- when :first, :last, :all
- send(args.first)
- else
- find_with_ids(*args)
- end
+ find_with_ids(*args)
end
end
@@ -128,18 +60,14 @@ module ActiveRecord
where(*args).first!
end
- # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the
- # same arguments to this method as you can to <tt>find(:first)</tt>.
- def first(*args)
- if args.any?
- if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
- limit(*args).to_a
- else
- apply_finder_options(args.first).first
- end
- else
- find_first
- end
+ # Examples:
+ #
+ # 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
+ def first(limit = nil)
+ limit ? limit(limit).to_a : find_first
end
# Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
@@ -148,18 +76,17 @@ module ActiveRecord
first 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)
- if args.any?
- if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
- if order_values.empty?
- order("#{primary_key} DESC").limit(*args).reverse
- else
- to_a.last(*args)
- end
+ # Examples:
+ #
+ # 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
+ def last(limit = nil)
+ if limit
+ if order_values.empty?
+ order("#{primary_key} DESC").limit(limit).reverse
else
- apply_finder_options(args.first).last
+ to_a.last(limit)
end
else
find_last
@@ -172,10 +99,16 @@ module ActiveRecord
last 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)
- args.any? ? apply_finder_options(args.first).to_a : to_a
+ # Examples:
+ #
+ # 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
@@ -234,12 +167,12 @@ module ActiveRecord
end
def construct_join_dependency_for_association_find
- including = (@eager_load_values + @includes_values).uniq
+ including = (eager_load_values + includes_values).uniq
ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
end
def construct_relation_for_association_calculations
- including = (@eager_load_values + @includes_values).uniq
+ including = (eager_load_values + includes_values).uniq
join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, arel.froms.first)
relation = except(:includes, :eager_load, :preload)
apply_join_dependency(relation, join_dependency)
@@ -338,7 +271,7 @@ module ActiveRecord
id = id.id if ActiveRecord::Base === id
column = columns_hash[primary_key]
- substitute = connection.substitute_at(column, @bind_values.length)
+ substitute = connection.substitute_at(column, bind_values.length)
relation = where(table[primary_key].eq(substitute))
relation.bind_values += [[column, id]]
record = relation.first
@@ -356,15 +289,15 @@ module ActiveRecord
result = where(table[primary_key].in(ids)).all
expected_size =
- if @limit_value && ids.size > @limit_value
- @limit_value
+ if limit_value && ids.size > limit_value
+ limit_value
else
ids.size
end
# 11 ids with limit 3, offset 9 should give 2 results.
- if @offset_value && (ids.size - @offset_value < expected_size)
- expected_size = ids.size - @offset_value
+ if offset_value && (ids.size - offset_value < expected_size)
+ expected_size = ids.size - offset_value
end
if result.size == expected_size
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
new file mode 100644
index 0000000000..1c2a06328f
--- /dev/null
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -0,0 +1,112 @@
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/keys'
+
+module ActiveRecord
+ class Relation
+ class Merger
+ attr_reader :relation, :other
+
+ def initialize(relation, other)
+ @relation = relation
+
+ if other.default_scoped? && other.klass != relation.klass
+ @other = other.with_default_scope
+ else
+ @other = other
+ end
+ end
+
+ def merge
+ HashMerger.new(relation, other.values).merge
+ end
+ end
+
+ class HashMerger
+ attr_reader :relation, :values
+
+ def initialize(relation, values)
+ values.assert_valid_keys(*Relation::VALUE_METHODS)
+
+ @relation = relation
+ @values = values
+ end
+
+ def normal_values
+ Relation::SINGLE_VALUE_METHODS +
+ Relation::MULTI_VALUE_METHODS -
+ [:where, :order, :bind, :reverse_order, :lock, :create_with, :reordering]
+ end
+
+ def merge
+ normal_values.each do |name|
+ value = values[name]
+ relation.send("#{name}!", value) unless value.blank?
+ end
+
+ merge_multi_values
+ merge_single_values
+
+ relation
+ end
+
+ private
+
+ def merge_multi_values
+ relation.where_values = merged_wheres
+ relation.bind_values = merged_binds
+
+ if values[:reordering]
+ # override any order specified in the original relation
+ relation.reorder! values[:order]
+ elsif values[:order]
+ # merge in order_values from r
+ relation.order! values[:order]
+ end
+
+ relation.extend(*values[:extending]) unless values[:extending].blank?
+ end
+
+ def merge_single_values
+ relation.lock_value = values[:lock] unless relation.lock_value
+ relation.reverse_order_value = values[:reverse_order]
+
+ unless values[:create_with].blank?
+ relation.create_with_value = (relation.create_with_value || {}).merge(values[:create_with])
+ end
+ end
+
+ def merged_binds
+ if values[:bind]
+ (relation.bind_values + values[:bind]).uniq(&:first)
+ else
+ relation.bind_values
+ end
+ end
+
+ def merged_wheres
+ if values[:where]
+ merged_wheres = relation.where_values + values[:where]
+
+ unless relation.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_wheres
+ else
+ relation.where_values
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index d737b34115..855477eaed 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -5,37 +5,67 @@ module ActiveRecord
module QueryMethods
extend ActiveSupport::Concern
- attr_accessor :includes_values, :eager_load_values, :preload_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, :reordering_value, :reverse_order_value,
- :uniq_value, :references_values
+ 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
+ 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
+ CODE
+ end
+
+ def create_with_value
+ @values[:create_with] || {}
+ end
+
+ def create_with_value=(value)
+ @values[:create_with] = value
+ end
+
+ alias extensions extending_values
def includes(*args)
- args.reject! {|a| a.blank? }
+ args.empty? ? self : clone.includes!(*args)
+ end
- return self if args.empty?
+ def includes!(*args)
+ args.reject! {|a| a.blank? }
- relation = clone
- relation.includes_values = (relation.includes_values + args).flatten.uniq
- relation
+ self.includes_values = (includes_values + args).flatten.uniq
+ self
end
def eager_load(*args)
- return self if args.blank?
+ args.blank? ? self : clone.eager_load!(*args)
+ end
- relation = clone
- relation.eager_load_values += args
- relation
+ def eager_load!(*args)
+ self.eager_load_values += args
+ self
end
def preload(*args)
- return self if args.blank?
+ args.blank? ? self : clone.preload!(*args)
+ end
- relation = clone
- relation.preload_values += args
- relation
+ def preload!(*args)
+ self.preload_values += args
+ self
end
# Used to indicate that an association is referenced by an SQL string, and should
@@ -49,11 +79,12 @@ module ActiveRecord
# User.includes(:posts).where("posts.name = 'foo'").references(:posts)
# # => Query now knows the string references posts, so adds a JOIN
def references(*args)
- return self if args.blank?
+ args.blank? ? self : clone.references!(*args)
+ end
- relation = clone
- relation.references_values = (references_values + args.flatten.map(&:to_s)).uniq
- relation
+ def references!(*args)
+ self.references_values = (references_values + args.flatten.map(&:to_s)).uniq
+ self
end
# Works in two unique ways.
@@ -87,34 +118,40 @@ module ActiveRecord
# => ActiveModel::MissingAttributeError: missing attribute: other_field
def select(value = Proc.new)
if block_given?
- to_a.select {|*block_args| value.call(*block_args) }
+ to_a.select { |*block_args| value.call(*block_args) }
else
- relation = clone
- relation.select_values += Array.wrap(value)
- relation
+ clone.select!(value)
end
end
+ def select!(value)
+ self.select_values += Array.wrap(value)
+ self
+ end
+
def group(*args)
- return self if args.blank?
+ args.blank? ? self : clone.group!(*args)
+ end
- relation = clone
- relation.group_values += args.flatten
- relation
+ def group!(*args)
+ self.group_values += args.flatten
+ self
end
def order(*args)
- return self if args.blank?
+ args.blank? ? self : clone.order!(*args)
+ end
+ def order!(*args)
args = args.flatten
+
references = args.reject { |arg| Arel::Node === arg }
.map { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }
.compact
+ references!(references) if references.any?
- relation = clone
- relation = relation.references(references) if references.any?
- relation.order_values += args
- relation
+ self.order_values += args
+ self
end
# Replaces any existing order defined on the relation with the specified order.
@@ -128,72 +165,88 @@ module ActiveRecord
# generates a query with 'ORDER BY id ASC, name ASC'.
#
def reorder(*args)
- return self if args.blank?
+ args.blank? ? self : clone.reorder!(*args)
+ end
- relation = clone
- relation.reordering_value = true
- relation.order_values = args.flatten
- relation
+ def reorder!(*args)
+ self.reordering_value = true
+ self.order_values = args.flatten
+ self
end
def joins(*args)
- return self if args.compact.blank?
-
- relation = clone
+ args.compact.blank? ? self : clone.joins!(*args)
+ end
+ def joins!(*args)
args.flatten!
- relation.joins_values += args
- relation
+ self.joins_values += args
+ self
end
def bind(value)
- relation = clone
- relation.bind_values += [value]
- relation
+ clone.bind!(value)
+ end
+
+ def bind!(value)
+ self.bind_values += [value]
+ self
end
def where(opts, *rest)
- return self if opts.blank?
+ opts.blank? ? self : clone.where!(opts, *rest)
+ end
- relation = clone
- relation = relation.references(PredicateBuilder.references(opts)) if Hash === opts
- relation.where_values += build_where(opts, rest)
- relation
+ def where!(opts, *rest)
+ references!(PredicateBuilder.references(opts)) if Hash === opts
+
+ self.where_values += build_where(opts, rest)
+ self
end
def having(opts, *rest)
- return self if opts.blank?
+ opts.blank? ? self : clone.having!(opts, *rest)
+ end
- relation = clone
- relation = relation.references(PredicateBuilder.references(opts)) if Hash === opts
- relation.having_values += build_where(opts, rest)
- relation
+ def having!(opts, *rest)
+ references!(PredicateBuilder.references(opts)) if Hash === opts
+
+ self.having_values += build_where(opts, rest)
+ self
end
def limit(value)
- relation = clone
- relation.limit_value = value
- relation
+ clone.limit!(value)
+ end
+
+ def limit!(value)
+ self.limit_value = value
+ self
end
def offset(value)
- relation = clone
- relation.offset_value = value
- relation
+ clone.offset!(value)
+ end
+
+ def offset!(value)
+ self.offset_value = value
+ self
end
def lock(locks = true)
- relation = clone
+ clone.lock!(locks)
+ end
+ def lock!(locks = true)
case locks
when String, TrueClass, NilClass
- relation.lock_value = locks || true
+ self.lock_value = locks || true
else
- relation.lock_value = false
+ self.lock_value = false
end
- relation
+ self
end
# Returns a chainable relation with zero records, specifically an
@@ -230,21 +283,30 @@ module ActiveRecord
end
def readonly(value = true)
- relation = clone
- relation.readonly_value = value
- relation
+ clone.readonly!(value)
+ end
+
+ def readonly!(value = true)
+ self.readonly_value = value
+ self
end
def create_with(value)
- relation = clone
- relation.create_with_value = value ? create_with_value.merge(value) : {}
- relation
+ clone.create_with!(value)
+ end
+
+ def create_with!(value)
+ self.create_with_value = value ? create_with_value.merge(value) : {}
+ self
end
def from(value)
- relation = clone
- relation.from_value = value
- relation
+ clone.from!(value)
+ end
+
+ def from!(value)
+ self.from_value = value
+ self
end
# Specifies whether the records should be unique or not. For example:
@@ -258,9 +320,12 @@ module ActiveRecord
# User.select(:name).uniq.uniq(false)
# # => You can also remove the uniqueness
def uniq(value = true)
- relation = clone
- relation.uniq_value = value
- relation
+ clone.uniq!(value)
+ end
+
+ def uniq!(value = true)
+ self.uniq_value = value
+ self
end
# Used to extend a scope with additional methods, either through
@@ -299,20 +364,30 @@ module ActiveRecord
# # pagination code goes here
# end
# end
- def extending(*modules)
- modules << Module.new(&Proc.new) if block_given?
+ def extending(*modules, &block)
+ if modules.any? || block
+ clone.extending!(*modules, &block)
+ else
+ self
+ end
+ end
- return self if modules.empty?
+ def extending!(*modules, &block)
+ modules << Module.new(&block) if block_given?
- relation = clone
- relation.send(:apply_modules, modules.flatten)
- relation
+ self.extending_values = modules.flatten
+ extend(*extending_values) if extending_values.any?
+
+ self
end
def reverse_order
- relation = clone
- relation.reverse_order_value = !relation.reverse_order_value
- relation
+ clone.reverse_order!
+ end
+
+ def reverse_order!
+ self.reverse_order_value = !reverse_order_value
+ self
end
def arel
@@ -322,26 +397,26 @@ module ActiveRecord
def build_arel
arel = table.from table
- build_joins(arel, @joins_values) unless @joins_values.empty?
+ build_joins(arel, joins_values) unless joins_values.empty?
- collapse_wheres(arel, (@where_values - ['']).uniq)
+ collapse_wheres(arel, (where_values - ['']).uniq)
- arel.having(*@having_values.uniq.reject{|h| h.blank?}) unless @having_values.empty?
+ arel.having(*having_values.uniq.reject{|h| h.blank?}) unless having_values.empty?
- arel.take(connection.sanitize_limit(@limit_value)) if @limit_value
- arel.skip(@offset_value.to_i) if @offset_value
+ arel.take(connection.sanitize_limit(limit_value)) if limit_value
+ arel.skip(offset_value.to_i) if offset_value
- arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
+ arel.group(*group_values.uniq.reject{|g| g.blank?}) unless group_values.empty?
- order = @order_values
- order = reverse_sql_order(order) if @reverse_order_value
+ order = order_values
+ order = reverse_sql_order(order) if reverse_order_value
arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?
- build_select(arel, @select_values.uniq)
+ build_select(arel, select_values.uniq)
- arel.distinct(@uniq_value)
- arel.from(@from_value) if @from_value
- arel.lock(@lock_value) if @lock_value
+ arel.distinct(uniq_value)
+ arel.from(from_value) if from_value
+ arel.lock(lock_value) if lock_value
arel
end
@@ -443,13 +518,6 @@ module ActiveRecord
end
end
- def apply_modules(modules)
- unless modules.empty?
- @extensions += modules
- modules.each {|extension| extend(extension) }
- end
- end
-
def reverse_sql_order(order_query)
order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 19ec41e5ca..7bf9c16959 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -1,81 +1,26 @@
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'
module ActiveRecord
module SpawnMethods
- def merge(r)
- return self unless r
- return to_a & r if r.is_a?(Array)
-
- merged_relation = clone
-
- r = r.with_default_scope if r.default_scoped? && r.klass != klass
-
- Relation::ASSOCIATION_METHODS.each do |method|
- value = r.send(:"#{method}_values")
-
- unless value.empty?
- if method == :includes
- merged_relation = merged_relation.includes(value)
- else
- merged_relation.send(:"#{method}_values=", value)
- end
- end
- end
-
- (Relation::MULTI_VALUE_METHODS - [:joins, :where, :order, :binds]).each do |method|
- value = r.send(:"#{method}_values")
- next if value.empty?
-
- value += merged_relation.send(:"#{method}_values")
- merged_relation.send :"#{method}_values=", value
- end
-
- merged_relation.joins_values += r.joins_values
-
- merged_wheres = @where_values + r.where_values
-
- merged_binds = (@bind_values + r.bind_values).uniq(&:first)
-
- 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
- merged_relation.bind_values = merged_binds
-
- (Relation::SINGLE_VALUE_METHODS - [:lock, :create_with, :reordering]).each do |method|
- value = r.send(:"#{method}_value")
- merged_relation.send(:"#{method}_value=", value) unless value.nil?
+ def merge(other)
+ if other.is_a?(Array)
+ to_a & other
+ elsif other
+ clone.merge!(other)
+ else
+ self
end
+ end
- merged_relation.lock_value = r.lock_value unless merged_relation.lock_value
-
- merged_relation = merged_relation.create_with(r.create_with_value) unless r.create_with_value.empty?
-
- if (r.reordering_value)
- # override any order specified in the original relation
- merged_relation.reordering_value = true
- merged_relation.order_values = r.order_values
+ def merge!(other)
+ if other.is_a?(Hash)
+ Relation::HashMerger.new(self, other).merge
else
- # merge in order_values from r
- merged_relation.order_values += r.order_values
+ Relation::Merger.new(self, other).merge
end
-
- # Apply scope extension modules
- merged_relation.send :apply_modules, r.extensions
-
- merged_relation
end
# Removes from the query the condition(s) specified in +skips+.
@@ -86,20 +31,9 @@ module ActiveRecord
# 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)
+ result = self.class.new(@klass, table, values.except(*skips))
result.default_scoped = default_scoped
-
- ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - skips).each do |method|
- result.send(:"#{method}_values=", send(:"#{method}_values"))
- end
-
- (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.extend(*extending_values) if extending_values.any?
result
end
@@ -111,20 +45,9 @@ module ActiveRecord
# Post.order('id asc').only(:where, :order) # uses the specified order
#
def only(*onlies)
- result = self.class.new(@klass, table)
+ result = self.class.new(@klass, table, values.slice(*onlies))
result.default_scoped = default_scoped
-
- ((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.extend(*extending_values) if extending_values.any?
result
end
diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
index f3bb70fb41..8e6ef20285 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -14,6 +14,7 @@ module ActiveRecord
def create_migration_file
return unless options[:migration] && options[:parent].nil?
+ attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false
migration_template "migration.rb", "db/migrate/create_#{table_name}.rb"
end
@@ -27,7 +28,7 @@ module ActiveRecord
end
def attributes_with_index
- attributes.select { |a| a.has_index? || (a.reference? && options[:indexes]) }
+ attributes.select { |a| !a.reference? && a.has_index? }
end
def accessible_attributes
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index f7f44208b3..196de34094 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -61,8 +61,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_loading_conditions_with_or
- posts = authors(:david).posts.find(
- :all, :include => :comments, :references => :comments,
+ posts = authors(:david).posts.references(:comments).find(
+ :all, :include => :comments,
:conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'"
)
assert_nil posts.detect { |p| p.author_id != authors(:david).id },
@@ -276,25 +276,25 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_nested_loading_through_has_one_association_with_conditions
- aa = AuthorAddress.find(
+ aa = AuthorAddress.references(:author_addresses).find(
author_addresses(:david_address).id, :include => {:author => :posts},
- :conditions => "author_addresses.id > 0", :references => :author_addresses
+ :conditions => "author_addresses.id > 0"
)
assert_equal aa.author.posts.count, aa.author.posts.length
end
def test_nested_loading_through_has_one_association_with_conditions_on_association
- aa = AuthorAddress.find(
+ aa = AuthorAddress.references(:authors).find(
author_addresses(:david_address).id, :include => {:author => :posts},
- :conditions => "authors.id > 0", :references => :authors
+ :conditions => "authors.id > 0"
)
assert_equal aa.author.posts.count, aa.author.posts.length
end
def test_nested_loading_through_has_one_association_with_conditions_on_nested_association
- aa = AuthorAddress.find(
+ aa = AuthorAddress.references(:posts).find(
author_addresses(:david_address).id, :include => {:author => :posts},
- :conditions => "posts.id > 0", :references => :posts
+ :conditions => "posts.id > 0"
)
assert_equal aa.author.posts.count, aa.author.posts.length
end
@@ -564,9 +564,9 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_has_many_and_limit_and_high_offset_and_multiple_array_conditions
assert_queries(1) do
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10,
- :conditions => [ "authors.name = ? and comments.body = ?", 'David', 'go crazy' ],
- :references => [:authors, :comments])
+ posts = Post.references(:authors, :comments).
+ find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10,
+ :conditions => [ "authors.name = ? and comments.body = ?", 'David', 'go crazy' ])
assert_equal 0, posts.size
end
end
@@ -592,7 +592,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_count_performed_on_a_has_many_association_with_multi_table_conditional
author = authors(:david)
author_posts_without_comments = author.posts.select { |post| post.comments.blank? }
- assert_equal author_posts_without_comments.size, author.posts.count(:all, :include => :comments, :conditions => 'comments.id is null', :references => :comments)
+ assert_equal author_posts_without_comments.size, author.posts.includes(:comments).where('comments.id is null').references(:comments).count
end
def test_eager_count_performed_on_a_has_many_through_association_with_multi_table_conditional
@@ -628,75 +628,75 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_has_many_and_limit_and_conditions_on_the_eagers
- posts = authors(:david).posts.find(:all,
- :include => :comments,
- :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
- :references => :comments,
- :limit => 2
- )
+ posts =
+ authors(:david).posts
+ .includes(:comments)
+ .where("comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'")
+ .references(:comments)
+ .limit(2)
+ .to_a
assert_equal 2, posts.size
- count = Post.count(
- :include => [ :comments, :author ],
- :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
- :references => [:authors, :comments],
- :limit => 2
- )
+ count =
+ Post.includes(:comments, :author)
+ .where("authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')")
+ .references(:authors, :comments)
+ .limit(2)
+ .count
assert_equal count, posts.size
end
def test_eager_with_has_many_and_limit_and_scoped_conditions_on_the_eagers
posts = nil
- Post.send(:with_scope, :find => {
- :include => :comments,
- :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
- :references => :comments
- }) do
- posts = authors(:david).posts.find(:all, :limit => 2)
+ Post.includes(:comments)
+ .where("comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'")
+ .references(:comments)
+ .scoping do
+
+ posts = authors(:david).posts.limit(2).to_a
assert_equal 2, posts.size
end
- Post.send(:with_scope, :find => {
- :include => [ :comments, :author ],
- :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
- :references => [:authors, :comments]
- }) do
- count = Post.count(:limit => 2)
+ Post.includes(:comments, :author)
+ .where("authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')")
+ .references(:authors, :comments)
+ .scoping do
+
+ count = Post.limit(2).count
assert_equal count, posts.size
end
end
def test_eager_with_has_many_and_limit_and_scoped_and_explicit_conditions_on_the_eagers
Post.send(:with_scope, :find => { :conditions => "1=1" }) do
- posts = authors(:david).posts.find(:all,
- :include => :comments,
- :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
- :references => :comments,
- :limit => 2
- )
+ posts =
+ authors(:david).posts
+ .includes(:comments)
+ .where("comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'")
+ .references(:comments)
+ .limit(2)
+ .to_a
assert_equal 2, posts.size
- count = Post.count(
- :include => [ :comments, :author ],
- :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
- :references => [:authors, :comments],
- :limit => 2
- )
+ count = Post.includes(:comments, :author)
+ .where("authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')")
+ .references(:authors, :comments)
+ .limit(2)
+ .count
assert_equal count, posts.size
end
end
def test_eager_with_scoped_order_using_association_limiting_without_explicit_scope
- posts_with_explicit_order = Post.find(
- :all, :conditions => 'comments.id is not null', :references => :comments,
- :include => :comments, :order => 'posts.id DESC', :limit => 2
- )
- posts_with_scoped_order = Post.send(:with_scope, :find => {:order => 'posts.id DESC'}) do
- Post.find(
- :all, :conditions => 'comments.id is not null',
- :references => :comments, :include => :comments, :limit => 2
- )
+ posts_with_explicit_order =
+ Post.where('comments.id is not null').references(:comments)
+ .includes(:comments).order('posts.id DESC').limit(2)
+
+ posts_with_scoped_order = Post.order('posts.id DESC').scoping do
+ Post.where('comments.id is not null').references(:comments)
+ .includes(:comments).limit(2)
end
+
assert_equal posts_with_explicit_order, posts_with_scoped_order
end
@@ -844,10 +844,9 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_limited_eager_with_numeric_in_association
assert_equal(
people(:david, :susan),
- Person.find(
+ Person.references(:number1_fans_people).find(
:all, :include => [:readers, :primary_contact, :number1_fan],
:conditions => "number1_fans_people.first_name like 'M%'",
- :references => :number1_fans_people,
:order => 'people.id', :limit => 2, :offset => 0
)
)
@@ -965,11 +964,11 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_count_with_include
if current_adapter?(:SybaseAdapter)
- assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "len(comments.body) > 15", :references => :comments)
+ assert_equal 3, authors(:david).posts_with_comments.where("len(comments.body) > 15").references(:comments).count
elsif current_adapter?(:OpenBaseAdapter)
- assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(FETCHBLOB(comments.body)) > 15", :references => :comments)
+ assert_equal 3, authors(:david).posts_with_comments.where("length(FETCHBLOB(comments.body)) > 15").references(:comments).count
else
- assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(comments.body) > 15", :references => :comments)
+ assert_equal 3, authors(:david).posts_with_comments.where("length(comments.body) > 15").references(:comments).count
end
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index f457dfb9b3..f5f5b96aa7 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -681,8 +681,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_join_table_alias
assert_equal(
3,
- Developer.find(
- :all, :include => {:projects => :developers}, :references => :developers_projects_join,
+ Developer.references(:developers_projects_join).find(
+ :all, :include => {:projects => :developers},
:conditions => 'developers_projects_join.joined_on IS NOT NULL'
).size
)
@@ -697,9 +697,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal(
3,
- Developer.find(
+ Developer.references(:developers_projects_join).find(
:all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL',
- :references => :developers_projects_join, :group => group.join(",")
+ :group => group.join(",")
).size
)
end
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 301755249c..dde5b26a88 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -362,7 +362,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
assert_raise ActiveRecord::EagerLoadPolymorphicError do
- tags(:general).taggings.find(:all, :include => :taggable, :references => :bogus_table, :conditions => 'bogus_table.column = 1')
+ tags(:general).taggings.includes(:taggable).where('bogus_table.column = 1').references(:bogus_table).to_a
end
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 5fb49d540f..ef22936e18 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1503,7 +1503,11 @@ class BasicsTest < ActiveRecord::TestCase
after_seq = Joke.sequence_name
assert_not_equal before_columns, after_columns
- assert_not_equal before_seq, after_seq unless before_seq.blank? && after_seq.blank?
+ unless before_seq.nil? && after_seq.nil?
+ assert_not_equal before_seq, after_seq
+ assert_equal "cold_jokes_id_seq", before_seq
+ assert_equal "funny_jokes_id_seq", after_seq
+ end
end
def test_dont_clear_sequence_name_when_setting_explicitly
@@ -1514,7 +1518,7 @@ class BasicsTest < ActiveRecord::TestCase
Joke.table_name = "funny_jokes"
after_seq = Joke.sequence_name
- assert_equal before_seq, after_seq unless before_seq.blank? && after_seq.blank?
+ assert_equal before_seq, after_seq unless before_seq.nil? && after_seq.nil?
end
def test_dont_clear_inheritnce_column_when_setting_explicitly
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 0391319a00..48cb05e36e 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -49,12 +49,12 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_get_maximum_of_field_with_include
- assert_equal 55, Account.maximum(:credit_limit, :include => :firm, :references => :companies, :conditions => "companies.name != 'Summit'")
+ assert_equal 55, Account.where("companies.name != 'Summit'").references(:companies).includes(:firm).maximum(:credit_limit)
end
def test_should_get_maximum_of_field_with_scoped_include
- Account.send :with_scope, :find => { :include => :firm, :references => :companies, :conditions => "companies.name != 'Summit'" } do
- assert_equal 55, Account.maximum(:credit_limit)
+ Account.send :with_scope, :find => { :include => :firm } do
+ assert_equal 55, Account.where("companies.name != 'Summit'").references(:companies).maximum(:credit_limit)
end
end
@@ -270,10 +270,10 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_not_modify_options_when_using_includes
- options = {:conditions => 'companies.id > 1', :include => :firm, :references => :companies}
+ options = {:conditions => 'companies.id > 1', :include => :firm}
options_copy = options.dup
- Account.count(:all, options)
+ Account.references(:companies).count(:all, options)
assert_equal options_copy, options
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index da93500ce3..8dc9f761c2 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -96,6 +96,30 @@ module ActiveRecord
end
end
+ def test_full_pool_blocks
+ cs = @pool.size.times.map { @pool.checkout }
+ t = Thread.new { @pool.checkout }
+
+ # make sure our thread is in the timeout section
+ Thread.pass until t.status == "sleep"
+
+ connection = cs.first
+ connection.close
+ assert_equal connection, t.join.value
+ end
+
+ def test_removing_releases_latch
+ cs = @pool.size.times.map { @pool.checkout }
+ t = Thread.new { @pool.checkout }
+
+ # make sure our thread is in the timeout section
+ Thread.pass until t.status == "sleep"
+
+ connection = cs.first
+ @pool.remove connection
+ assert_respond_to t.join.value, :execute
+ end
+
def test_reap_and_active
@pool.checkout
@pool.checkout
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 96c8eb6417..70f3c4bcc5 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -1182,9 +1182,9 @@ class FinderTest < ActiveRecord::TestCase
end
def test_with_limiting_with_custom_select
- posts = Post.find(
+ posts = Post.references(:authors).find(
:all, :include => :author, :select => ' posts.*, authors.id as "author_id"',
- :references => :authors, :limit => 3, :order => 'posts.id'
+ :limit => 3, :order => 'posts.id'
)
assert_equal 3, posts.size
assert_equal [0, 1, 1], posts.map(&:author_id).sort
diff --git a/activerecord/test/cases/migration/references_index_test.rb b/activerecord/test/cases/migration/references_index_test.rb
new file mode 100644
index 0000000000..8ab1c59724
--- /dev/null
+++ b/activerecord/test/cases/migration/references_index_test.rb
@@ -0,0 +1,99 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class Migration
+ class ReferencesIndexTest < ActiveRecord::TestCase
+ attr_reader :connection, :table_name
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ @table_name = :testings
+ end
+
+ def teardown
+ super
+ connection.drop_table :testings rescue nil
+ end
+
+ def test_creates_index
+ connection.create_table table_name do |t|
+ t.references :foo, :index => true
+ end
+
+ assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ end
+
+ def test_does_not_create_index
+ connection.create_table table_name do |t|
+ t.references :foo
+ end
+
+ refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ end
+
+ def test_does_not_create_index_explicit
+ connection.create_table table_name do |t|
+ t.references :foo, :index => false
+ end
+
+ refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ end
+
+ def test_creates_index_with_options
+ connection.create_table table_name do |t|
+ t.references :foo, :index => {:name => :index_testings_on_yo_momma}
+ t.references :bar, :index => {:unique => true}
+ end
+
+ assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_yo_momma)
+ assert connection.index_exists?(table_name, :bar_id, :name => :index_testings_on_bar_id, :unique => true)
+ end
+
+ def test_creates_polymorphic_index
+ connection.create_table table_name do |t|
+ t.references :foo, :polymorphic => true, :index => true
+ end
+
+ assert connection.index_exists?(table_name, [:foo_id, :foo_type], :name => :index_testings_on_foo_id_and_foo_type)
+ end
+
+ def test_creates_index_for_existing_table
+ connection.create_table table_name
+ connection.change_table table_name do |t|
+ t.references :foo, :index => true
+ end
+
+ assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ end
+
+ def test_does_not_create_index_for_existing_table
+ connection.create_table table_name
+ connection.change_table table_name do |t|
+ t.references :foo
+ end
+
+ refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ end
+
+ def test_does_not_create_index_for_existing_table_explicit
+ connection.create_table table_name
+ connection.change_table table_name do |t|
+ t.references :foo, :index => false
+ end
+
+ refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ end
+
+ def test_creates_polymorphic_index_for_existing_table
+ connection.create_table table_name
+ connection.change_table table_name do |t|
+ t.references :foo, :polymorphic => true, :index => true
+ end
+
+ assert connection.index_exists?(table_name, [:foo_id, :foo_type], :name => :index_testings_on_foo_id_and_foo_type)
+ end
+
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 92dc150104..e14c2d072c 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -36,7 +36,7 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Base.connection.initialize_schema_migrations_table
ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}"
- %w(things awesome_things prefix_things_suffix prefix_awesome_things_suffix).each do |table|
+ %w(things awesome_things prefix_things_suffix p_awesome_things_s ).each do |table|
Thing.connection.drop_table(table) rescue nil
end
Thing.reset_column_information
@@ -278,8 +278,8 @@ class MigrationTest < ActiveRecord::TestCase
def test_rename_table_with_prefix_and_suffix
assert !Thing.table_exists?
- ActiveRecord::Base.table_name_prefix = 'prefix_'
- ActiveRecord::Base.table_name_suffix = '_suffix'
+ ActiveRecord::Base.table_name_prefix = 'p_'
+ ActiveRecord::Base.table_name_suffix = '_s'
Thing.reset_table_name
Thing.reset_sequence_name
WeNeedThings.up
@@ -288,7 +288,7 @@ class MigrationTest < ActiveRecord::TestCase
assert_equal "hello world", Thing.find(:first).content
RenameThings.up
- Thing.table_name = "prefix_awesome_things_suffix"
+ Thing.table_name = "p_awesome_things_s"
assert_equal "hello world", Thing.find(:first).content
ensure
@@ -404,6 +404,7 @@ end
class ChangeTableMigrationsTest < ActiveRecord::TestCase
def setup
@connection = Person.connection
+ @connection.stubs(:add_index)
@connection.create_table :delete_me, :force => true do |t|
end
end
diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb
index f7a5d05582..e0fdd2871c 100644
--- a/activerecord/test/cases/modules_test.rb
+++ b/activerecord/test/cases/modules_test.rb
@@ -72,7 +72,7 @@ class ModulesTest < ActiveRecord::TestCase
clients = []
assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do
- clients << MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL', :references => :accounts)
+ clients << MyApplication::Business::Client.references(:accounts).find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL')
clients << MyApplication::Business::Client.find(3, :include => {:firm => :account})
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index ac6dee3c6a..9ef703fd9b 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -19,33 +19,12 @@ module ActiveRecord
assert !relation.loaded, 'relation is not loaded'
end
- def test_single_values
- assert_equal [:limit, :offset, :lock, :readonly, :from, :reordering, :reverse_order, :uniq].map(&:to_s).sort,
- Relation::SINGLE_VALUE_METHODS.map(&:to_s).sort
- end
-
def test_initialize_single_values
relation = Relation.new :a, :b
- Relation::SINGLE_VALUE_METHODS.each do |method|
+ (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method|
assert_nil relation.send("#{method}_value"), method.to_s
end
- end
-
- def test_association_methods
- assert_equal [:includes, :eager_load, :preload].map(&:to_s).sort,
- Relation::ASSOCIATION_METHODS.map(&:to_s).sort
- end
-
- def test_initialize_association_methods
- relation = Relation.new :a, :b
- Relation::ASSOCIATION_METHODS.each do |method|
- assert_equal [], relation.send("#{method}_values"), method.to_s
- end
- end
-
- def test_multi_value_methods
- assert_equal [:select, :group, :order, :joins, :where, :having, :bind, :references].map(&:to_s).sort,
- Relation::MULTI_VALUE_METHODS.map(&:to_s).sort
+ assert_equal({}, relation.create_with_value)
end
def test_multi_value_initialize
@@ -64,19 +43,19 @@ module ActiveRecord
relation = Relation.new :a, :b
assert_equal({}, relation.where_values_hash)
- relation.where_values << :hello
+ relation.where! :hello
assert_equal({}, relation.where_values_hash)
end
def test_has_values
relation = Relation.new Post, Post.arel_table
- relation.where_values << relation.table[:id].eq(10)
+ relation.where! relation.table[:id].eq(10)
assert_equal({:id => 10}, relation.where_values_hash)
end
def test_values_wrong_table
relation = Relation.new Post, Post.arel_table
- relation.where_values << Comment.arel_table[:id].eq(10)
+ relation.where! Comment.arel_table[:id].eq(10)
assert_equal({}, relation.where_values_hash)
end
@@ -85,7 +64,7 @@ module ActiveRecord
left = relation.table[:id].eq(10)
right = relation.table[:id].eq(10)
combine = left.and right
- relation.where_values << combine
+ relation.where! combine
assert_equal({}, relation.where_values_hash)
end
@@ -108,7 +87,7 @@ module ActiveRecord
def test_create_with_value_with_wheres
relation = Relation.new Post, Post.arel_table
- relation.where_values << relation.table[:id].eq(10)
+ relation.where! relation.table[:id].eq(10)
relation.create_with_value = {:hello => 'world'}
assert_equal({:hello => 'world', :id => 10}, relation.scope_for_create)
end
@@ -118,7 +97,7 @@ module ActiveRecord
relation = Relation.new Post, Post.arel_table
assert_equal({}, relation.scope_for_create)
- relation.where_values << relation.table[:id].eq(10)
+ relation.where! relation.table[:id].eq(10)
assert_equal({}, relation.scope_for_create)
relation.create_with_value = {:hello => 'world'}
@@ -132,7 +111,7 @@ module ActiveRecord
def test_eager_load_values
relation = Relation.new :a, :b
- relation.eager_load_values << :b
+ relation.eager_load! :b
assert relation.eager_loading?
end
@@ -149,10 +128,101 @@ module ActiveRecord
assert_equal ['foo'], relation.references_values
end
- def test_apply_finder_options_takes_references
+ test 'merging a hash into a relation' do
relation = Relation.new :a, :b
- relation = relation.apply_finder_options(:references => :foo)
- assert_equal ['foo'], relation.references_values
+ relation = relation.merge where: ['lol'], readonly: true
+
+ assert_equal ['lol'], relation.where_values
+ assert_equal true, relation.readonly_value
+ end
+
+ test 'merging an empty hash into a relation' do
+ assert_equal [], Relation.new(:a, :b).merge({}).where_values
+ end
+
+ test 'merging a hash with unknown keys raises' do
+ assert_raises(ArgumentError) { Relation::HashMerger.new(nil, omg: 'lol') }
+ end
+
+ test '#values returns a dup of the values' do
+ relation = Relation.new(:a, :b).where! :foo
+ values = relation.values
+
+ values[:where] = nil
+ assert_not_nil relation.where_values
+ end
+
+ test 'relations can be created with a values hash' do
+ relation = Relation.new(:a, :b, where: [:foo])
+ assert_equal [:foo], relation.where_values
+ end
+ end
+
+ class RelationMutationTest < ActiveSupport::TestCase
+ def relation
+ @relation ||= Relation.new :a, :b
+ end
+
+ (Relation::MULTI_VALUE_METHODS - [:references, :extending]).each do |method|
+ test "##{method}!" do
+ assert relation.public_send("#{method}!", :foo).equal?(relation)
+ assert_equal [:foo], relation.public_send("#{method}_values")
+ end
+ end
+
+ test '#references!' do
+ assert relation.references!(:foo).equal?(relation)
+ assert relation.references_values.include?('foo')
+ end
+
+ test 'extending!' do
+ mod = Module.new
+
+ assert relation.extending!(mod).equal?(relation)
+ assert [mod], relation.extending_values
+ assert relation.is_a?(mod)
+ end
+
+ test 'extending! with empty args' do
+ relation.extending!
+ assert_equal [], relation.extending_values
+ end
+
+ (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with]).each do |method|
+ test "##{method}!" do
+ assert relation.public_send("#{method}!", :foo).equal?(relation)
+ assert_equal :foo, relation.public_send("#{method}_value")
+ end
+ end
+
+ test '#lock!' do
+ assert relation.lock!('foo').equal?(relation)
+ assert_equal 'foo', relation.lock_value
+ end
+
+ test '#reorder!' do
+ relation = self.relation.order('foo')
+
+ assert relation.reorder!('bar').equal?(relation)
+ assert_equal ['bar'], relation.order_values
+ assert relation.reordering_value
+ end
+
+ test 'reverse_order!' do
+ assert relation.reverse_order!.equal?(relation)
+ assert relation.reverse_order_value
+ relation.reverse_order!
+ assert !relation.reverse_order_value
+ end
+
+ test 'create_with!' do
+ assert relation.create_with!(foo: 'bar').equal?(relation)
+ assert_equal({foo: 'bar'}, relation.create_with_value)
+ end
+
+ test 'merge!' do
+ assert relation.merge!(where: ['foo']).equal?(relation)
+ assert_equal ['foo'], relation.where_values
end
end
end