aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md23
-rw-r--r--activerecord/activerecord.gemspec21
-rw-r--r--activerecord/lib/active_record/associations.rb6
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb12
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb10
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb12
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb10
-rw-r--r--activerecord/lib/active_record/base.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb20
-rw-r--r--activerecord/lib/active_record/core.rb3
-rw-r--r--activerecord/lib/active_record/fixtures.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb6
-rw-r--r--activerecord/lib/active_record/null_relation.rb6
-rw-r--r--activerecord/lib/active_record/railtie.rb33
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb4
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb24
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb6
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb10
-rw-r--r--activerecord/lib/active_record/timestamp.rb3
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb17
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb26
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb19
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb6
-rw-r--r--activerecord/test/cases/base_test.rb7
-rw-r--r--activerecord/test/cases/calculations_test.rb6
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb8
-rw-r--r--activerecord/test/cases/finder_test.rb5
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb21
-rw-r--r--activerecord/test/models/topic.rb7
33 files changed, 262 insertions, 84 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 28d8b066e0..580a580ba5 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,28 @@
## Rails 4.0.0 (unreleased) ##
+* `#pluck` can be used on a relation with `select` clause
+ Fix #7551
+
+ Example:
+
+ Topic.select([:approved, :id]).order(:id).pluck(:id)
+
+ *Yves Senn*
+
+* Do not create useless database transaction when building `has_one` association.
+
+ Example:
+
+ User.has_one :profile
+ User.new.build_profile
+
+ *Bogdan Gusiev*
+
+* :counter_cache option for `has_many` associations to support custom named counter caches.
+ Fix #7993
+
+ *Yves Senn*
+
* Deprecate the possibility to pass a string as third argument of `add_index`.
Pass `unique: true` instead.
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 53791d96ef..31ddb01123 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -1,4 +1,4 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
+version = File.read(File.expand_path('../../RAILS_VERSION', __FILE__)).strip
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
@@ -8,21 +8,22 @@ Gem::Specification.new do |s|
s.description = 'Databases on Rails. Build a persistent domain model by mapping database tables to Ruby classes. Strong conventions for associations, validations, aggregations, migrations, and testing come baked-in.'
s.required_ruby_version = '>= 1.9.3'
- s.license = 'MIT'
- s.author = 'David Heinemeier Hansson'
- s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.license = 'MIT'
+
+ s.author = 'David Heinemeier Hansson'
+ s.email = 'david@loudthinking.com'
+ s.homepage = 'http://www.rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'examples/**/*', 'lib/**/*']
s.require_path = 'lib'
- s.extra_rdoc_files = %w( README.rdoc )
+ s.extra_rdoc_files = %w(README.rdoc)
s.rdoc_options.concat ['--main', 'README.rdoc']
- s.add_dependency('activesupport', version)
- s.add_dependency('activemodel', version)
- s.add_dependency('arel', '~> 3.0.2')
+ s.add_dependency 'activesupport', version
+ s.add_dependency 'activemodel', version
- s.add_dependency('activerecord-deprecated_finders', '0.0.1')
+ s.add_dependency 'arel', '~> 3.0.2'
+ s.add_dependency 'activerecord-deprecated_finders', '0.0.1'
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 69b95f814c..5949269f2a 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1,6 +1,9 @@
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/string/conversions'
require 'active_support/core_ext/module/remove_method'
+require 'active_support/dependencies/autoload'
+require 'active_support/concern'
+require 'active_record/errors'
module ActiveRecord
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
@@ -1122,6 +1125,9 @@ module ActiveRecord
# If using with the <tt>:through</tt> option, the association on the join model must be
# a +belongs_to+, and the records which get deleted are the join records, rather than
# the associated records.
+ # [:counter_cache]
+ # This option can be used to configure a custom named <tt>:counter_cache.</tt> You only need this option,
+ # when you customized the name of your <tt>:counter_cache</tt> on the <tt>belongs_to</tt> association.
# [:as]
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# [:through]
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index 1b382f7285..fcdfc1e150 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -1,5 +1,8 @@
+require 'active_record/associations'
+
module ActiveRecord::Associations::Builder
class CollectionAssociation < Association #:nodoc:
+
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
def valid_options
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index ab8225460a..0d1bdd21ee 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder
end
def valid_options
- super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of]
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache]
end
def valid_dependent_options
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 54215cf88d..862ff201de 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -174,8 +174,6 @@ module ActiveRecord
# association, it will be used for the query. Otherwise, construct options and pass them with
# scope to the target class's +count+.
def count(column_name = nil, count_options = {})
- return 0 if owner.new_record?
-
column_name, count_options = nil, column_name if column_name.is_a?(Hash)
if options[:counter_sql] || options[:finder_sql]
@@ -366,6 +364,16 @@ module ActiveRecord
record
end
+ def scope(opts = {})
+ scope = super()
+ scope.none! if opts.fetch(:nullify, true) && null_scope?
+ scope
+ end
+
+ def null_scope?
+ owner.new_record? && !foreign_key_present?
+ end
+
private
def custom_counter_sql
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index e73f940334..e444b0ed83 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -28,10 +28,12 @@ module ActiveRecord
# is computed directly through SQL and does not trigger by itself the
# instantiation of the actual post records.
class CollectionProxy < Relation
+ delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
+
def initialize(association) #:nodoc:
@association = association
super association.klass, association.klass.arel_table
- merge! association.scope
+ merge! association.scope(nullify: false)
end
def target
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 74864d271f..f59565ae77 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -76,7 +76,7 @@ module ActiveRecord
end
def cached_counter_attribute_name(reflection = reflection)
- "#{reflection.name}_count"
+ options[:counter_cache] || "#{reflection.name}_count"
end
def update_counter(difference, reflection = reflection)
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 06bead41de..ee816d2392 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -28,7 +28,7 @@ module ActiveRecord
# If target and record are nil, or target is equal to record,
# we don't need to have transaction.
if (target || record) && target != record
- reflection.klass.transaction do
+ transaction_if(save) do
remove_target!(options[:dependent]) if target && !target.destroyed?
if record
@@ -90,6 +90,14 @@ module ActiveRecord
def nullify_owner_attributes(record)
record[reflection.foreign_key] = nil
end
+
+ def transaction_if(value)
+ if value
+ reflection.klass.transaction { yield }
+ else
+ yield
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 437fd00948..e0bfdb8f3e 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -369,14 +369,10 @@ module ActiveRecord
end
def typecasted_attribute_value(name)
- if self.class.serialized_attributes.include?(name)
- @attributes[name].serialized_value
- else
- # FIXME: we need @attributes to be used consistently.
- # If the values stored in @attributes were already typecasted, this code
- # could be simplified
- read_attribute(name)
- end
+ # FIXME: we need @attributes to be used consistently.
+ # If the values stored in @attributes were already typecasted, this code
+ # could be simplified
+ read_attribute(name)
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 5b9ed81424..47d4a938af 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -5,7 +5,7 @@ module ActiveRecord
included do
# Returns a hash of all the attributes that have been specified for
- # serialization as keys and their class restriction as values.
+ # serialization as keys and their class restriction as values.
class_attribute :serialized_attributes, instance_accessor: false
self.serialized_attributes = {}
end
@@ -129,6 +129,14 @@ module ActiveRecord
end
end
end
+
+ def typecasted_attribute_value(name)
+ if self.class.serialized_attributes.include?(name)
+ @attributes[name].serialized_value
+ else
+ super
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index eabbd80f66..83047c1845 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -8,7 +8,6 @@ require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/hash/deep_merge'
-require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/string/behavior'
require 'active_support/core_ext/kernel/singleton_class'
@@ -333,7 +332,6 @@ module ActiveRecord #:nodoc:
extend Translation
extend DynamicMatchers
extend Explain
- extend ConnectionHandling
include Persistence
include ReadonlyAttributes
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 1da95f451f..db0db272a6 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -1,7 +1,7 @@
require 'thread'
require 'monitor'
require 'set'
-require 'active_support/core_ext/module/deprecation'
+require 'active_support/deprecation'
module ActiveRecord
# Raised when a connection could not be obtained within the connection
@@ -494,10 +494,18 @@ module ActiveRecord
@class_to_pool = Hash.new { |h,k| h[k] = {} }
end
- def connection_pools
+ def connection_pool_list
owner_to_pool.values.compact
end
+ def connection_pools
+ ActiveSupport::Deprecation.warn(
+ "In the next release, this will return the same as #connection_pool_list. " \
+ "(An array of pools, rather than a hash mapping specs to pools.)"
+ )
+ Hash[connection_pool_list.map { |pool| [pool.spec, pool] }]
+ end
+
def establish_connection(owner, spec)
@class_to_pool.clear
owner_to_pool[owner] = ConnectionAdapters::ConnectionPool.new(spec)
@@ -506,23 +514,23 @@ module ActiveRecord
# Returns true if there are any active connections among the connection
# pools that the ConnectionHandler is managing.
def active_connections?
- connection_pools.any?(&:active_connection?)
+ connection_pool_list.any?(&:active_connection?)
end
# Returns any connections in use by the current thread back to the pool,
# and also returns connections to the pool cached by threads that are no
# longer alive.
def clear_active_connections!
- connection_pools.each(&:release_connection)
+ connection_pool_list.each(&:release_connection)
end
# Clears the cache which maps classes.
def clear_reloadable_connections!
- connection_pools.each(&:clear_reloadable_connections!)
+ connection_pool_list.each(&:clear_reloadable_connections!)
end
def clear_all_connections!
- connection_pools.each(&:disconnect!)
+ connection_pool_list.each(&:disconnect!)
end
# Locate the connection of the nearest super class. This can be an
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 98032db2ef..7b4710e6b3 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -3,9 +3,6 @@ require 'active_support/core_ext/object/duplicable'
require 'thread'
module ActiveRecord
- ActiveSupport.on_load(:active_record_config) do
- end
-
module Core
extend ActiveSupport::Concern
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 29a99a5336..79d37147d0 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -883,7 +883,7 @@ module ActiveRecord
end
def enlist_fixture_connections
- ActiveRecord::Base.connection_handler.connection_pools.map(&:connection)
+ ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
end
private
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index c3b9a0f9b7..22347fcaef 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -642,7 +642,11 @@ module ActiveRecord
def proper_table_name(name)
# Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
- name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
+ if name.respond_to? :table_name
+ name.table_name
+ else
+ "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
+ end
end
def migrations_paths
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 4c1c91e3df..711fc8b883 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -46,7 +46,11 @@ module ActiveRecord
{}
end
- def count
+ def count(*)
+ 0
+ end
+
+ def sum(*)
0
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 77e41ea927..5464ca6066 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -92,6 +92,33 @@ module ActiveRecord
initializer "active_record.set_configs" do |app|
ActiveSupport.on_load(:active_record) do
+ begin
+ old_behavior, ActiveSupport::Deprecation.behavior = ActiveSupport::Deprecation.behavior, :stderr
+ whitelist_attributes = app.config.active_record.delete(:whitelist_attributes)
+
+ if respond_to?(:mass_assignment_sanitizer=)
+ mass_assignment_sanitizer = nil
+ else
+ mass_assignment_sanitizer = app.config.active_record.delete(:mass_assignment_sanitizer)
+ end
+
+ unless whitelist_attributes.nil? && mass_assignment_sanitizer.nil?
+ ActiveSupport::Deprecation.warn <<-EOF.strip_heredoc, []
+ Model based mass assignment security has been extracted
+ out of Rails into a gem. Please use the new recommended protection model for
+ params or add `protected_attributes` to your Gemfile to use the old one.
+
+ To disable this message remove the `whitelist_attributes` option from your
+ `config/application.rb` file and any `mass_assignment_sanitizer` options
+ from your `config/environments/*.rb` files.
+
+ See http://edgeguides.rubyonrails.org/security.html#mass-assignment for more information
+ EOF
+ end
+ ensure
+ ActiveSupport::Deprecation.behavior = old_behavior
+ end
+
app.config.active_record.each do |k,v|
send "#{k}=", v
end
@@ -122,8 +149,10 @@ module ActiveRecord
ActiveSupport.on_load(:active_record) do
ActionDispatch::Reloader.send(hook) do
- ActiveRecord::Base.clear_reloadable_connections!
- ActiveRecord::Base.clear_cache!
+ if ActiveRecord::Base.connected?
+ ActiveRecord::Base.clear_reloadable_connections!
+ ActiveRecord::Base.clear_cache!
+ end
end
end
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index a7d2f4bd24..df27318678 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -165,7 +165,9 @@ module ActiveRecord
if has_include?(column_names.first)
construct_relation_for_association_calculations.pluck(*column_names)
else
- result = klass.connection.select_all(select(column_names).arel, nil, bind_values)
+ relation = spawn
+ relation.select_values = column_names
+ result = klass.connection.select_all(relation.arel, nil, bind_values)
columns = result.columns.map do |key|
klass.column_types.fetch(key) {
result.column_types.fetch(key) {
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index ab8b36c8ab..dbfa92bbbd 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -1,3 +1,4 @@
+require 'thread'
module ActiveRecord
module Delegation # :nodoc:
@@ -6,6 +7,8 @@ module ActiveRecord
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
:connection, :columns_hash, :auto_explain_threshold_in_seconds, :to => :klass
+ @@delegation_mutex = Mutex.new
+
def self.delegate_to_scoped_klass(method)
if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
module_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -32,13 +35,28 @@ module ActiveRecord
def method_missing(method, *args, &block)
if @klass.respond_to?(method)
- ::ActiveRecord::Delegation.delegate_to_scoped_klass(method)
+ @@delegation_mutex.synchronize do
+ unless ::ActiveRecord::Delegation.method_defined?(method)
+ ::ActiveRecord::Delegation.delegate_to_scoped_klass(method)
+ end
+ end
+
scoping { @klass.send(method, *args, &block) }
elsif Array.method_defined?(method)
- ::ActiveRecord::Delegation.delegate method, :to => :to_a
+ @@delegation_mutex.synchronize do
+ unless ::ActiveRecord::Delegation.method_defined?(method)
+ ::ActiveRecord::Delegation.delegate method, :to => :to_a
+ end
+ end
+
to_a.send(method, *args, &block)
elsif arel.respond_to?(method)
- ::ActiveRecord::Delegation.delegate method, :to => :arel
+ @@delegation_mutex.synchronize do
+ unless ::ActiveRecord::Delegation.method_defined?(method)
+ ::ActiveRecord::Delegation.delegate method, :to => :arel
+ end
+ end
+
arel.send(method, *args, &block)
else
super
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 99c2f45bc8..af67b2ba6c 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/hash/indifferent_access'
-
module ActiveRecord
module FinderMethods
# 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]).
@@ -225,7 +223,7 @@ module ActiveRecord
def construct_limited_ids_condition(relation)
orders = relation.order_values.map { |val| val.presence }.compact
- values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders)
+ values = @klass.connection.distinct("#{quoted_table_name}.#{primary_key}", orders)
relation = relation.dup
@@ -234,8 +232,6 @@ module ActiveRecord
end
def find_with_ids(*ids)
- return to_a.find { |*block_args| yield(*block_args) } if block_given?
-
expects_array = ids.first.kind_of?(Array)
return ids.first if expects_array && ids.first.empty?
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 4fdc296c7e..0817bb6d81 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -218,7 +218,6 @@ module ActiveRecord
# Like #order, but modifies relation in place.
def order!(*args)
args.flatten!
-
validate_order_args args
references = args.reject { |arg| Arel::Node === arg }
@@ -245,7 +244,6 @@ module ActiveRecord
# Like #reorder, but modifies relation in place.
def reorder!(*args)
args.flatten!
-
validate_order_args args
self.reordering_value = true
@@ -796,7 +794,7 @@ module ActiveRecord
def reverse_sql_order(order_query)
order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
- order_query.map do |o|
+ order_query.flat_map do |o|
case o
when Arel::Nodes::Ordering
o.reverse
@@ -814,7 +812,7 @@ module ActiveRecord
else
o
end
- end.flatten
+ end
end
def array_of_strings?(o)
@@ -825,7 +823,7 @@ module ActiveRecord
orders = order_values
orders = reverse_sql_order(orders) if reverse_order_value
- orders = orders.uniq.reject(&:blank?).map do |order|
+ orders = orders.uniq.reject(&:blank?).flat_map do |order|
case order
when Symbol
table[order].asc
@@ -834,7 +832,7 @@ module ActiveRecord
else
order
end
- end.flatten
+ end
arel.order(*orders) unless orders.empty?
end
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 920d6848c1..cf17b1d8a4 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -1,8 +1,5 @@
module ActiveRecord
- ActiveSupport.on_load(:active_record_config) do
- end
-
# = Active Record Timestamp
#
# Active Record automatically timestamps create and update operations if the
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 42f5b69d4e..1b1b479f1a 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
@@ -799,12 +799,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, developer.projects.count
end
- def test_counting_should_not_fire_sql_if_parent_is_unsaved
- assert_no_queries do
- assert_equal 0, Developer.new.projects.count
- end
- end
-
unless current_adapter?(:PostgreSQLAdapter)
def test_count_with_finder_sql
assert_equal 3, projects(:active_record).developers_with_finder_sql.count
@@ -862,4 +856,15 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def klass.name; 'Foo'; end
assert_deprecated { klass.has_and_belongs_to_many :posts, :delete_sql => 'lol' }
end
+
+ test "has and belongs to many associations on new records use null relations" do
+ projects = Developer.new.projects
+ assert_no_queries do
+ assert_equal [], projects
+ assert_equal [], projects.where(title: 'omg')
+ assert_equal [], projects.pluck(:title)
+ assert_equal 0, projects.count
+ end
+ end
+
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 50c23c863f..b1066fb24c 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -262,12 +262,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal firm.limited_clients.length, firm.limited_clients.count
end
- def test_counting_should_not_fire_sql_if_parent_is_unsaved
- assert_no_queries do
- assert_equal 0, Person.new.readers.count
- end
- end
-
def test_finding
assert_equal 2, Firm.all.merge!(:order => "id").first.clients.length
end
@@ -754,6 +748,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_custom_named_counter_cache
+ topic = topics(:first)
+
+ assert_difference "topic.reload.replies_count", -1 do
+ topic.approved_replies.clear
+ end
+ end
+
def test_deleting_a_collection
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
@@ -1648,4 +1650,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
klass = Class.new(ActiveRecord::Base)
assert_deprecated { klass.has_many :foo, :counter_sql => 'lol' }
end
+
+ test "has many associations on new records use null relations" do
+ post = Post.new
+
+ assert_no_queries do
+ assert_equal [], post.comments
+ assert_equal [], post.comments.where(body: 'omg')
+ assert_equal [], post.comments.pluck(:body)
+ assert_equal 0, post.comments.sum(:id)
+ assert_equal 0, post.comments.count
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index b2a5d9d6f7..8e52ce1d91 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -766,12 +766,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal 1, authors(:mary).categories.general.count
end
- def test_counting_should_not_fire_sql_if_parent_is_unsaved
- assert_no_queries do
- assert_equal 0, Person.new.posts.count
- end
- end
-
def test_has_many_through_belongs_to_should_update_when_the_through_foreign_key_changes
post = posts(:eager_other)
@@ -876,4 +870,17 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
post = tags(:general).tagged_posts.create! :title => "foo", :body => "bar"
assert_equal [tags(:general)], post.reload.tags
end
+
+ test "has many through associations on new records use null relations" do
+ person = Person.new
+
+ assert_no_queries do
+ assert_equal [], person.posts
+ assert_equal [], person.posts.where(body: 'omg')
+ assert_equal [], person.posts.pluck(:body)
+ assert_equal 0, person.posts.sum(:tags_count)
+ assert_equal 0, person.posts.count
+ end
+ end
+
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 2d3cb654df..ea1cfa0805 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -206,6 +206,12 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal account, firm.account
end
+ def test_build_association_dont_create_transaction
+ assert_no_queries {
+ Firm.new.build_account
+ }
+ end
+
def test_build_and_create_should_not_happen_within_scope
pirate = pirates(:blackbeard)
scoped_count = pirate.association(:foo_bulb).scope.where_values.count
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 203e44857a..c02d8f7760 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1442,6 +1442,13 @@ class BasicsTest < ActiveRecord::TestCase
assert_match(/\/#{dev.id}$/, dev.cache_key)
end
+ def test_cache_key_format_is_precise_enough
+ dev = Developer.first
+ key = dev.cache_key
+ dev.touch
+ assert_not_equal key, dev.cache_key
+ end
+
def test_uniq_delegates_to_scoped
scope = stub
Bird.stubs(:all).returns(mock(:uniq => scope))
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index abbf2a765e..65d28ea028 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -580,4 +580,10 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal ["Over There"], Possession.pluck(:where)
end
+
+ def test_pluck_replaces_select_clause
+ taks_relation = Topic.select(:approved, :id).order(:id)
+ assert_equal [1,2,3,4], taks_relation.pluck(:id)
+ assert_equal [false, true, true, true], taks_relation.pluck(:approved)
+ end
end
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 631bf1aaac..2ddabe058f 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -8,7 +8,7 @@ module ActiveRecord
@subklass = Class.new(@klass)
@handler = ConnectionHandler.new
- @handler.establish_connection @klass, Base.connection_pool.spec
+ @pool = @handler.establish_connection(@klass, Base.connection_pool.spec)
end
def test_retrieve_connection
@@ -44,6 +44,12 @@ module ActiveRecord
assert_same @handler.retrieve_connection_pool(@klass),
@handler.retrieve_connection_pool(@subklass)
end
+
+ def test_connection_pools
+ assert_deprecated do
+ assert_equal({ Base.connection_pool.spec => @pool }, @handler.connection_pools)
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index d44ac21b05..7db7953313 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -610,6 +610,11 @@ class FinderTest < ActiveRecord::TestCase
assert_nil Topic.find_by_heading("The First Topic!")
end
+ def test_find_by_one_attribute_bang_with_blank_defined
+ blank_topic = BlankTopic.create(title: "The Blank One")
+ assert_equal blank_topic, BlankTopic.find_by_title!("The Blank One")
+ end
+
def test_find_by_one_attribute_with_conditions
assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50)
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index fe9eddbdec..3f08f9ea4d 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -185,6 +185,17 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
assert_equal "James", mean_pirate.parrot.name
assert_equal "blue", mean_pirate.parrot.color
end
+
+ def test_accepts_nested_attributes_for_can_be_overridden_in_subclasses
+ Pirate.accepts_nested_attributes_for(:parrot)
+
+ mean_pirate_class = Class.new(Pirate) do
+ accepts_nested_attributes_for :parrot
+ end
+ mean_pirate = mean_pirate_class.new
+ mean_pirate.parrot_attributes = { :name => "James" }
+ assert_equal "James", mean_pirate.parrot.name
+ end
end
class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
@@ -464,17 +475,15 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
end
def test_should_unset_association_when_an_existing_record_is_destroyed
- @ship.reload
original_pirate_id = @ship.pirate.id
- @ship.attributes = {:pirate_attributes => {:id => @ship.pirate.id, :_destroy => true}}
- @ship.save!
+ @ship.update_attributes! pirate_attributes: { id: @ship.pirate.id, _destroy: true }
- assert_empty Pirate.where(["id = ?", original_pirate_id])
+ assert_empty Pirate.where(id: original_pirate_id)
assert_nil @ship.pirate_id
assert_nil @ship.pirate
@ship.reload
- assert_empty Pirate.where(["id = ?", original_pirate_id])
+ assert_empty Pirate.where(id: original_pirate_id)
assert_nil @ship.pirate_id
assert_nil @ship.pirate
end
@@ -491,7 +500,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
@ship.update_attributes(:pirate_attributes => { :id => @ship.pirate.id, :_destroy => '1' })
assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload }
-
+ ensure
Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
end
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 4b27c16681..f7f4cebc5a 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -33,6 +33,7 @@ class Topic < ActiveRecord::Base
end
has_many :replies, :dependent => :destroy, :foreign_key => "parent_id"
+ has_many :approved_replies, -> { approved }, class_name: 'Reply', foreign_key: "parent_id", counter_cache: 'replies_count'
has_many :replies_with_primary_key, :class_name => "Reply", :dependent => :destroy, :primary_key => "title", :foreign_key => "parent_title"
has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id"
@@ -106,6 +107,12 @@ class ImportantTopic < Topic
serialize :important, Hash
end
+class BlankTopic < Topic
+ def blank?
+ true
+ end
+end
+
module Web
class Topic < ActiveRecord::Base
has_many :replies, :dependent => :destroy, :foreign_key => "parent_id", :class_name => 'Web::Reply'