diff options
Diffstat (limited to 'activerecord')
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' |