diff options
Diffstat (limited to 'activerecord/lib')
21 files changed, 131 insertions, 49 deletions
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 |