aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record')
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb31
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb34
-rw-r--r--activerecord/lib/active_record/associations/preloader/belongs_to.rb10
-rw-r--r--activerecord/lib/active_record/associations/preloader/collection_association.rb16
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_many.rb10
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_many_through.rb21
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_one.rb10
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_one_through.rb11
-rw-r--r--activerecord/lib/active_record/associations/preloader/singular_association.rb15
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb122
-rw-r--r--activerecord/lib/active_record/attribute.rb242
-rw-r--r--activerecord/lib/active_record/attribute/user_provided_default.rb32
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb96
-rw-r--r--activerecord/lib/active_record/attribute_mutation_tracker.rb111
-rw-r--r--activerecord/lib/active_record/attribute_set.rb113
-rw-r--r--activerecord/lib/active_record/attribute_set/builder.rb126
-rw-r--r--activerecord/lib/active_record/attribute_set/yaml_encoder.rb43
-rw-r--r--activerecord/lib/active_record/attributes.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb2
-rw-r--r--activerecord/lib/active_record/legacy_yaml_adapter.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb2
-rw-r--r--activerecord/lib/active_record/model_schema.rb6
-rw-r--r--activerecord/lib/active_record/railties/databases.rake19
-rw-r--r--activerecord/lib/active_record/railties/jdbcmysql_error.rb18
-rw-r--r--activerecord/lib/active_record/relation/query_attribute.rb4
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb4
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb15
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb47
28 files changed, 115 insertions, 1053 deletions
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index e1754d4a19..e1087be9b3 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -44,16 +44,8 @@ module ActiveRecord
extend ActiveSupport::Autoload
eager_autoload do
- autoload :Association, "active_record/associations/preloader/association"
- autoload :SingularAssociation, "active_record/associations/preloader/singular_association"
- autoload :CollectionAssociation, "active_record/associations/preloader/collection_association"
- autoload :ThroughAssociation, "active_record/associations/preloader/through_association"
-
- autoload :HasMany, "active_record/associations/preloader/has_many"
- autoload :HasManyThrough, "active_record/associations/preloader/has_many_through"
- autoload :HasOne, "active_record/associations/preloader/has_one"
- autoload :HasOneThrough, "active_record/associations/preloader/has_one_through"
- autoload :BelongsTo, "active_record/associations/preloader/belongs_to"
+ autoload :Association, "active_record/associations/preloader/association"
+ autoload :ThroughAssociation, "active_record/associations/preloader/through_association"
end
# Eager loads the named associations for the given Active Record record(s).
@@ -166,8 +158,6 @@ module ActiveRecord
end
class AlreadyLoaded # :nodoc:
- attr_reader :owners, :reflection
-
def initialize(klass, owners, reflection, preload_scope)
@owners = owners
@reflection = reflection
@@ -178,11 +168,13 @@ module ActiveRecord
def preloaded_records
owners.flat_map { |owner| owner.association(reflection.name).target }
end
+
+ protected
+ attr_reader :owners, :reflection
end
# Returns a class containing the logic needed to load preload the data
- # and attach it to a relation. For example +Preloader::Association+ or
- # +Preloader::HasManyThrough+. The class returned implements a `run` method
+ # and attach it to a relation. The class returned implements a `run` method
# that accepts a preloader.
def preloader_for(reflection, owners)
if owners.first.association(reflection.name).loaded?
@@ -190,13 +182,10 @@ module ActiveRecord
end
reflection.check_preloadable!
- case reflection.macro
- when :has_many
- reflection.options[:through] ? HasManyThrough : HasMany
- when :has_one
- reflection.options[:through] ? HasOneThrough : HasOne
- when :belongs_to
- BelongsTo
+ if reflection.options[:through]
+ ThroughAssociation
+ else
+ Association
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 607d376a08..735da152b7 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -4,7 +4,6 @@ module ActiveRecord
module Associations
class Preloader
class Association #:nodoc:
- attr_reader :owners, :reflection, :preload_scope, :model, :klass
attr_reader :preloaded_records
def initialize(klass, owners, reflection, preload_scope)
@@ -17,11 +16,20 @@ module ActiveRecord
end
def run(preloader)
- associated_records_by_owner(preloader).each do |owner, records|
- associate_records_to_owner(owner, records)
+ records = load_records do |record|
+ owner = owners_by_key[convert_key(record[association_key_name])]
+ association = owner.association(reflection.name)
+ association.set_inverse_instance(record)
+ end
+
+ owners.each do |owner|
+ associate_records_to_owner(owner, records[convert_key(owner[owner_key_name])] || [])
end
end
+ protected
+ attr_reader :owners, :reflection, :preload_scope, :model, :klass
+
private
# The name of the key on the associated records
def association_key_name
@@ -33,20 +41,14 @@ module ActiveRecord
reflection.join_foreign_key
end
- def associated_records_by_owner(preloader)
- records = load_records do |record|
- owner = owners_by_key[convert_key(record[association_key_name])]
- association = owner.association(reflection.name)
- association.set_inverse_instance(record)
- end
-
- owners.each_with_object({}) do |owner, result|
- result[owner] = records[convert_key(owner[owner_key_name])] || []
- end
- end
-
def associate_records_to_owner(owner, records)
- raise NotImplementedError
+ association = owner.association(reflection.name)
+ if reflection.collection?
+ association.loaded!
+ association.target.concat(records)
+ else
+ association.target = records.first
+ end
end
def owner_keys
diff --git a/activerecord/lib/active_record/associations/preloader/belongs_to.rb b/activerecord/lib/active_record/associations/preloader/belongs_to.rb
deleted file mode 100644
index a8e3340b23..0000000000
--- a/activerecord/lib/active_record/associations/preloader/belongs_to.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveRecord
- module Associations
- class Preloader
- class BelongsTo < SingularAssociation #:nodoc:
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/preloader/collection_association.rb b/activerecord/lib/active_record/associations/preloader/collection_association.rb
deleted file mode 100644
index fc2029f54a..0000000000
--- a/activerecord/lib/active_record/associations/preloader/collection_association.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveRecord
- module Associations
- class Preloader
- class CollectionAssociation < Association #:nodoc:
- private
- def associate_records_to_owner(owner, records)
- association = owner.association(reflection.name)
- association.loaded!
- association.target.concat(records)
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/preloader/has_many.rb b/activerecord/lib/active_record/associations/preloader/has_many.rb
deleted file mode 100644
index 72f55bc43f..0000000000
--- a/activerecord/lib/active_record/associations/preloader/has_many.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveRecord
- module Associations
- class Preloader
- class HasMany < CollectionAssociation #:nodoc:
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/preloader/has_many_through.rb b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
deleted file mode 100644
index 0639fdca44..0000000000
--- a/activerecord/lib/active_record/associations/preloader/has_many_through.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveRecord
- module Associations
- class Preloader
- class HasManyThrough < CollectionAssociation #:nodoc:
- include ThroughAssociation
-
- def associated_records_by_owner(preloader)
- records_by_owner = super
-
- if reflection_scope.distinct_value
- records_by_owner.each_value(&:uniq!)
- end
-
- records_by_owner
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/preloader/has_one.rb b/activerecord/lib/active_record/associations/preloader/has_one.rb
deleted file mode 100644
index e339b65fb5..0000000000
--- a/activerecord/lib/active_record/associations/preloader/has_one.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveRecord
- module Associations
- class Preloader
- class HasOne < SingularAssociation #:nodoc:
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/preloader/has_one_through.rb b/activerecord/lib/active_record/associations/preloader/has_one_through.rb
deleted file mode 100644
index 17734d0257..0000000000
--- a/activerecord/lib/active_record/associations/preloader/has_one_through.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveRecord
- module Associations
- class Preloader
- class HasOneThrough < SingularAssociation #:nodoc:
- include ThroughAssociation
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/preloader/singular_association.rb b/activerecord/lib/active_record/associations/preloader/singular_association.rb
deleted file mode 100644
index 30a92411e3..0000000000
--- a/activerecord/lib/active_record/associations/preloader/singular_association.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveRecord
- module Associations
- class Preloader
- class SingularAssociation < Association #:nodoc:
- private
- def associate_records_to_owner(owner, records)
- association = owner.association(reflection.name)
- association.target = records.first
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index b16fca7dc9..a6b7ab80a2 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -3,96 +3,54 @@
module ActiveRecord
module Associations
class Preloader
- module ThroughAssociation #:nodoc:
- def through_reflection
- reflection.through_reflection
- end
-
- def source_reflection
- reflection.source_reflection
- end
-
- def associated_records_by_owner(preloader)
- already_loaded = owners.first.association(through_reflection.name).loaded?
- through_scope = through_scope()
-
- unless already_loaded
- preloader.preload(owners, through_reflection.name, through_scope)
- end
-
- through_records = owners.map do |owner|
- center = owner.association(through_reflection.name).target
- [owner, Array(center)]
- end
+ class ThroughAssociation < Association # :nodoc:
+ def run(preloader)
+ already_loaded = owners.first.association(through_reflection.name).loaded?
+ through_scope = through_scope()
+ reflection_scope = target_reflection_scope
+ through_preloaders = preloader.preload(owners, through_reflection.name, through_scope)
+ middle_records = through_preloaders.flat_map(&:preloaded_records)
+ preloaders = preloader.preload(middle_records, source_reflection.name, reflection_scope)
+ @preloaded_records = preloaders.flat_map(&:preloaded_records)
- if already_loaded
- if source_type = reflection.options[:source_type]
- through_records.map! do |owner, center|
- center = center.select do |record|
+ owners.each do |owner|
+ through_records = Array(owner.association(through_reflection.name).target)
+ if already_loaded
+ if source_type = reflection.options[:source_type]
+ through_records = through_records.select do |record|
record[reflection.foreign_type] == source_type
end
- [owner, center]
end
+ else
+ owner.association(through_reflection.name).reset if through_scope
end
- else
- reset_association(owners, through_reflection.name, through_scope)
- end
-
- middle_records = through_records.flat_map(&:last)
-
- if preload_scope
- reflection_scope = reflection_scope().merge(preload_scope)
- elsif reflection.scope
- reflection_scope = reflection_scope()
- end
-
- preloaders = preloader.preload(middle_records,
- source_reflection.name,
- reflection_scope)
-
- @preloaded_records = preloaders.flat_map(&:preloaded_records)
-
- middle_to_pl = preloaders.each_with_object({}) do |pl, h|
- pl.owners.each { |middle|
- h[middle] = pl
- }
- end
-
- through_records.each_with_object({}) do |(lhs, center), records_by_owner|
- pl_to_middle = center.group_by { |record| middle_to_pl[record] }
-
- records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
- rhs_records = middles.flat_map { |r|
- r.association(source_reflection.name).target
- }.compact
-
- # Respect the order on `reflection_scope` if it exists, else use the natural order.
- if reflection_scope && !reflection_scope.order_values.empty?
- @id_map ||= id_to_index_map @preloaded_records
- rhs_records.sort_by { |rhs| @id_map[rhs] }
- else
- rhs_records
- end
+ result = through_records.flat_map do |record|
+ association = record.association(source_reflection.name)
+ target = association.target
+ association.reset if preload_scope
+ target
end
- end.tap do
- reset_association(middle_records, source_reflection.name, preload_scope)
+ result.compact!
+ if reflection_scope
+ result.sort_by! { |rhs| preload_index[rhs] } if reflection_scope.order_values.any?
+ result.uniq! if reflection_scope.distinct_value
+ end
+ associate_records_to_owner(owner, result)
end
end
private
+ def through_reflection
+ reflection.through_reflection
+ end
- def id_to_index_map(ids)
- id_map = {}
- ids.each_with_index { |id, index| id_map[id] = index }
- id_map
+ def source_reflection
+ reflection.source_reflection
end
- def reset_association(owners, association_name, should_reset)
- # Don't cache the association - we would only be caching a subset
- if should_reset
- owners.each { |owner|
- owner.association(association_name).reset
- }
+ def preload_index
+ @preload_index ||= @preloaded_records.each_with_object({}).with_index do |(id, result), index|
+ result[id] = index
end
end
@@ -133,6 +91,16 @@ module ActiveRecord
scope unless scope.empty_scope?
end
+
+ def target_reflection_scope
+ if preload_scope
+ reflection_scope.merge(preload_scope)
+ elsif reflection.scope
+ reflection_scope
+ else
+ nil
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
deleted file mode 100644
index fc474edc15..0000000000
--- a/activerecord/lib/active_record/attribute.rb
+++ /dev/null
@@ -1,242 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveRecord
- class Attribute # :nodoc:
- class << self
- def from_database(name, value, type)
- FromDatabase.new(name, value, type)
- end
-
- def from_user(name, value, type, original_attribute = nil)
- FromUser.new(name, value, type, original_attribute)
- end
-
- def with_cast_value(name, value, type)
- WithCastValue.new(name, value, type)
- end
-
- def null(name)
- Null.new(name)
- end
-
- def uninitialized(name, type)
- Uninitialized.new(name, type)
- end
- end
-
- attr_reader :name, :value_before_type_cast, :type
-
- # This method should not be called directly.
- # Use #from_database or #from_user
- def initialize(name, value_before_type_cast, type, original_attribute = nil)
- @name = name
- @value_before_type_cast = value_before_type_cast
- @type = type
- @original_attribute = original_attribute
- end
-
- def value
- # `defined?` is cheaper than `||=` when we get back falsy values
- @value = type_cast(value_before_type_cast) unless defined?(@value)
- @value
- end
-
- def original_value
- if assigned?
- original_attribute.original_value
- else
- type_cast(value_before_type_cast)
- end
- end
-
- def value_for_database
- type.serialize(value)
- end
-
- def changed?
- changed_from_assignment? || changed_in_place?
- end
-
- def changed_in_place?
- has_been_read? && type.changed_in_place?(original_value_for_database, value)
- end
-
- def forgetting_assignment
- with_value_from_database(value_for_database)
- end
-
- def with_value_from_user(value)
- type.assert_valid_value(value)
- self.class.from_user(name, value, type, original_attribute || self)
- end
-
- def with_value_from_database(value)
- self.class.from_database(name, value, type)
- end
-
- def with_cast_value(value)
- self.class.with_cast_value(name, value, type)
- end
-
- def with_type(type)
- if changed_in_place?
- with_value_from_user(value).with_type(type)
- else
- self.class.new(name, value_before_type_cast, type, original_attribute)
- end
- end
-
- def type_cast(*)
- raise NotImplementedError
- end
-
- def initialized?
- true
- end
-
- def came_from_user?
- false
- end
-
- def has_been_read?
- defined?(@value)
- end
-
- def ==(other)
- self.class == other.class &&
- name == other.name &&
- value_before_type_cast == other.value_before_type_cast &&
- type == other.type
- end
- alias eql? ==
-
- def hash
- [self.class, name, value_before_type_cast, type].hash
- end
-
- def init_with(coder)
- @name = coder["name"]
- @value_before_type_cast = coder["value_before_type_cast"]
- @type = coder["type"]
- @original_attribute = coder["original_attribute"]
- @value = coder["value"] if coder.map.key?("value")
- end
-
- def encode_with(coder)
- coder["name"] = name
- coder["value_before_type_cast"] = value_before_type_cast unless value_before_type_cast.nil?
- coder["type"] = type if type
- coder["original_attribute"] = original_attribute if original_attribute
- coder["value"] = value if defined?(@value)
- end
-
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
- protected
-
- attr_reader :original_attribute
- alias_method :assigned?, :original_attribute
-
- def original_value_for_database
- if assigned?
- original_attribute.original_value_for_database
- else
- _original_value_for_database
- end
- end
-
- private
- def initialize_dup(other)
- if defined?(@value) && @value.duplicable?
- @value = @value.dup
- end
- end
-
- def changed_from_assignment?
- assigned? && type.changed?(original_value, value, value_before_type_cast)
- end
-
- def _original_value_for_database
- type.serialize(original_value)
- end
-
- class FromDatabase < Attribute # :nodoc:
- def type_cast(value)
- type.deserialize(value)
- end
-
- def _original_value_for_database
- value_before_type_cast
- end
- end
-
- class FromUser < Attribute # :nodoc:
- def type_cast(value)
- type.cast(value)
- end
-
- def came_from_user?
- !type.value_constructed_by_mass_assignment?(value_before_type_cast)
- end
- end
-
- class WithCastValue < Attribute # :nodoc:
- def type_cast(value)
- value
- end
-
- def changed_in_place?
- false
- end
- end
-
- class Null < Attribute # :nodoc:
- def initialize(name)
- super(name, nil, Type.default_value)
- end
-
- def type_cast(*)
- nil
- end
-
- def with_type(type)
- self.class.with_cast_value(name, nil, type)
- end
-
- def with_value_from_database(value)
- raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
- end
- alias_method :with_value_from_user, :with_value_from_database
- end
-
- class Uninitialized < Attribute # :nodoc:
- UNINITIALIZED_ORIGINAL_VALUE = Object.new
-
- def initialize(name, type)
- super(name, nil, type)
- end
-
- def value
- if block_given?
- yield name
- end
- end
-
- def original_value
- UNINITIALIZED_ORIGINAL_VALUE
- end
-
- def value_for_database
- end
-
- def initialized?
- false
- end
-
- def with_type(type)
- self.class.new(name, type)
- end
- end
- private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
- end
-end
diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb
deleted file mode 100644
index f746960fac..0000000000
--- a/activerecord/lib/active_record/attribute/user_provided_default.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# frozen_string_literal: true
-
-require "active_record/attribute"
-
-module ActiveRecord
- class Attribute # :nodoc:
- class UserProvidedDefault < FromUser # :nodoc:
- def initialize(name, value, type, database_default)
- @user_provided_value = value
- super(name, value, type, database_default)
- end
-
- def value_before_type_cast
- if user_provided_value.is_a?(Proc)
- @memoized_value_before_type_cast ||= user_provided_value.call
- else
- @user_provided_value
- end
- end
-
- def with_type(type)
- self.class.new(name, user_provided_value, type, original_attribute)
- end
-
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
- protected
-
- attr_reader :user_provided_value
- end
- end
-end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 79110d04f4..3de6fe566d 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "active_support/core_ext/module/attribute_accessors"
-require "active_record/attribute_mutation_tracker"
module ActiveRecord
module AttributeMethods
@@ -33,65 +32,13 @@ module ActiveRecord
# <tt>reload</tt> the record and clears changed attributes.
def reload(*)
super.tap do
+ @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
@mutations_before_last_save = nil
+ @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
@mutations_from_database = nil
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
end
- def initialize_dup(other) # :nodoc:
- super
- @attributes = self.class._default_attributes.map do |attr|
- attr.with_value_from_user(@attributes.fetch_value(attr.name))
- end
- @mutations_from_database = nil
- end
-
- def changes_applied # :nodoc:
- @mutations_before_last_save = mutations_from_database
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
- forget_attribute_assignments
- @mutations_from_database = nil
- end
-
- def clear_changes_information # :nodoc:
- @mutations_before_last_save = nil
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
- forget_attribute_assignments
- @mutations_from_database = nil
- end
-
- def clear_attribute_changes(attr_names) # :nodoc:
- super
- attr_names.each do |attr_name|
- clear_attribute_change(attr_name)
- end
- end
-
- def changed_attributes # :nodoc:
- # This should only be set by methods which will call changed_attributes
- # multiple times when it is known that the computed value cannot change.
- if defined?(@cached_changed_attributes)
- @cached_changed_attributes
- else
- super.reverse_merge(mutations_from_database.changed_values).freeze
- end
- end
-
- def changes # :nodoc:
- cache_changed_attributes do
- super
- end
- end
-
- def previous_changes # :nodoc:
- mutations_before_last_save.changes
- end
-
- def attribute_changed_in_place?(attr_name) # :nodoc:
- mutations_from_database.changed_in_place?(attr_name)
- end
-
# Did this attribute change when we last saved? This method can be invoked
# as +saved_change_to_name?+ instead of <tt>saved_change_to_attribute?("name")</tt>.
# Behaves similarly to +attribute_changed?+. This method is useful in
@@ -182,26 +129,6 @@ module ActiveRecord
result
end
- def mutations_from_database
- unless defined?(@mutations_from_database)
- @mutations_from_database = nil
- end
- @mutations_from_database ||= AttributeMutationTracker.new(@attributes)
- end
-
- def changes_include?(attr_name)
- super || mutations_from_database.changed?(attr_name)
- end
-
- def clear_attribute_change(attr_name)
- mutations_from_database.forget_change(attr_name)
- end
-
- def attribute_will_change!(attr_name)
- super
- mutations_from_database.force_change(attr_name)
- end
-
def _update_record(*)
partial_writes? ? super(keys_for_partial_write) : super
end
@@ -213,25 +140,6 @@ module ActiveRecord
def keys_for_partial_write
changed_attribute_names_to_save & self.class.column_names
end
-
- def forget_attribute_assignments
- @attributes = @attributes.map(&:forgetting_assignment)
- end
-
- def mutations_before_last_save
- @mutations_before_last_save ||= NullMutationTracker.instance
- end
-
- def cache_changed_attributes
- @cached_changed_attributes = changed_attributes
- yield
- ensure
- clear_changed_attributes_cache
- end
-
- def clear_changed_attributes_cache
- remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
- end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb
deleted file mode 100644
index 94bf641a5d..0000000000
--- a/activerecord/lib/active_record/attribute_mutation_tracker.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveRecord
- class AttributeMutationTracker # :nodoc:
- OPTION_NOT_GIVEN = Object.new
-
- def initialize(attributes)
- @attributes = attributes
- @forced_changes = Set.new
- end
-
- def changed_values
- attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
- if changed?(attr_name)
- result[attr_name] = attributes[attr_name].original_value
- end
- end
- end
-
- def changes
- attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
- change = change_to_attribute(attr_name)
- if change
- result[attr_name] = change
- end
- end
- end
-
- def change_to_attribute(attr_name)
- attr_name = attr_name.to_s
- if changed?(attr_name)
- [attributes[attr_name].original_value, attributes.fetch_value(attr_name)]
- end
- end
-
- def any_changes?
- attr_names.any? { |attr| changed?(attr) }
- end
-
- def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN)
- attr_name = attr_name.to_s
- forced_changes.include?(attr_name) ||
- attributes[attr_name].changed? &&
- (OPTION_NOT_GIVEN == from || attributes[attr_name].original_value == from) &&
- (OPTION_NOT_GIVEN == to || attributes[attr_name].value == to)
- end
-
- def changed_in_place?(attr_name)
- attributes[attr_name.to_s].changed_in_place?
- end
-
- def forget_change(attr_name)
- attr_name = attr_name.to_s
- attributes[attr_name] = attributes[attr_name].forgetting_assignment
- forced_changes.delete(attr_name)
- end
-
- def original_value(attr_name)
- attributes[attr_name.to_s].original_value
- end
-
- def force_change(attr_name)
- forced_changes << attr_name.to_s
- end
-
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
- protected
-
- attr_reader :attributes, :forced_changes
-
- private
-
- def attr_names
- attributes.keys
- end
- end
-
- class NullMutationTracker # :nodoc:
- include Singleton
-
- def changed_values(*)
- {}
- end
-
- def changes(*)
- {}
- end
-
- def change_to_attribute(attr_name)
- end
-
- def any_changes?(*)
- false
- end
-
- def changed?(*)
- false
- end
-
- def changed_in_place?(*)
- false
- end
-
- def forget_change(*)
- end
-
- def original_value(*)
- end
- end
-end
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb
deleted file mode 100644
index 9785666e77..0000000000
--- a/activerecord/lib/active_record/attribute_set.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-# frozen_string_literal: true
-
-require "active_record/attribute_set/builder"
-require "active_record/attribute_set/yaml_encoder"
-
-module ActiveRecord
- class AttributeSet # :nodoc:
- delegate :each_value, :fetch, to: :attributes
-
- def initialize(attributes)
- @attributes = attributes
- end
-
- def [](name)
- attributes[name] || Attribute.null(name)
- end
-
- def []=(name, value)
- attributes[name] = value
- end
-
- def values_before_type_cast
- attributes.transform_values(&:value_before_type_cast)
- end
-
- def to_hash
- initialized_attributes.transform_values(&:value)
- end
- alias_method :to_h, :to_hash
-
- def key?(name)
- attributes.key?(name) && self[name].initialized?
- end
-
- def keys
- attributes.each_key.select { |name| self[name].initialized? }
- end
-
- if defined?(JRUBY_VERSION)
- # This form is significantly faster on JRuby, and this is one of our biggest hotspots.
- # https://github.com/jruby/jruby/pull/2562
- def fetch_value(name, &block)
- self[name].value(&block)
- end
- else
- def fetch_value(name)
- self[name].value { |n| yield n if block_given? }
- end
- end
-
- def write_from_database(name, value)
- attributes[name] = self[name].with_value_from_database(value)
- end
-
- def write_from_user(name, value)
- attributes[name] = self[name].with_value_from_user(value)
- end
-
- def write_cast_value(name, value)
- attributes[name] = self[name].with_cast_value(value)
- end
-
- def freeze
- @attributes.freeze
- super
- end
-
- def deep_dup
- self.class.allocate.tap do |copy|
- copy.instance_variable_set(:@attributes, attributes.deep_dup)
- end
- end
-
- def initialize_dup(_)
- @attributes = attributes.dup
- super
- end
-
- def initialize_clone(_)
- @attributes = attributes.clone
- super
- end
-
- def reset(key)
- if key?(key)
- write_from_database(key, nil)
- end
- end
-
- def accessed
- attributes.select { |_, attr| attr.has_been_read? }.keys
- end
-
- def map(&block)
- new_attributes = attributes.transform_values(&block)
- AttributeSet.new(new_attributes)
- end
-
- def ==(other)
- attributes == other.attributes
- end
-
- protected
-
- attr_reader :attributes
-
- private
-
- def initialized_attributes
- attributes.select { |_, attr| attr.initialized? }
- end
- end
-end
diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb
deleted file mode 100644
index 349cc7e403..0000000000
--- a/activerecord/lib/active_record/attribute_set/builder.rb
+++ /dev/null
@@ -1,126 +0,0 @@
-# frozen_string_literal: true
-
-require "active_record/attribute"
-
-module ActiveRecord
- class AttributeSet # :nodoc:
- class Builder # :nodoc:
- attr_reader :types, :always_initialized, :default
-
- def initialize(types, always_initialized = nil, &default)
- @types = types
- @always_initialized = always_initialized
- @default = default
- end
-
- def build_from_database(values = {}, additional_types = {})
- if always_initialized && !values.key?(always_initialized)
- values[always_initialized] = nil
- end
-
- attributes = LazyAttributeHash.new(types, values, additional_types, &default)
- AttributeSet.new(attributes)
- end
- end
- end
-
- class LazyAttributeHash # :nodoc:
- delegate :transform_values, :each_key, :each_value, :fetch, to: :materialize
-
- def initialize(types, values, additional_types, &default)
- @types = types
- @values = values
- @additional_types = additional_types
- @materialized = false
- @delegate_hash = {}
- @default = default || proc {}
- end
-
- def key?(key)
- delegate_hash.key?(key) || values.key?(key) || types.key?(key)
- end
-
- def [](key)
- delegate_hash[key] || assign_default_value(key)
- end
-
- def []=(key, value)
- if frozen?
- raise RuntimeError, "Can't modify frozen hash"
- end
- delegate_hash[key] = value
- end
-
- def deep_dup
- dup.tap do |copy|
- copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup))
- end
- end
-
- def initialize_dup(_)
- @delegate_hash = Hash[delegate_hash]
- super
- end
-
- def select
- keys = types.keys | values.keys | delegate_hash.keys
- keys.each_with_object({}) do |key, hash|
- attribute = self[key]
- if yield(key, attribute)
- hash[key] = attribute
- end
- end
- end
-
- def ==(other)
- if other.is_a?(LazyAttributeHash)
- materialize == other.materialize
- else
- materialize == other
- end
- end
-
- def marshal_dump
- materialize
- end
-
- def marshal_load(delegate_hash)
- @delegate_hash = delegate_hash
- @types = {}
- @values = {}
- @additional_types = {}
- @materialized = true
- end
-
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
- protected
-
- attr_reader :types, :values, :additional_types, :delegate_hash, :default
-
- def materialize
- unless @materialized
- values.each_key { |key| self[key] }
- types.each_key { |key| self[key] }
- unless frozen?
- @materialized = true
- end
- end
- delegate_hash
- end
-
- private
-
- def assign_default_value(name)
- type = additional_types.fetch(name, types[name])
- value_present = true
- value = values.fetch(name) { value_present = false }
-
- if value_present
- delegate_hash[name] = Attribute.from_database(name, value, type)
- elsif types.key?(name)
- delegate_hash[name] = default.call(name) || Attribute.uninitialized(name, type)
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb
deleted file mode 100644
index 9254ce16ab..0000000000
--- a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveRecord
- class AttributeSet
- # Attempts to do more intelligent YAML dumping of an
- # ActiveRecord::AttributeSet to reduce the size of the resulting string
- class YAMLEncoder # :nodoc:
- def initialize(default_types)
- @default_types = default_types
- end
-
- def encode(attribute_set, coder)
- coder["concise_attributes"] = attribute_set.each_value.map do |attr|
- if attr.type.equal?(default_types[attr.name])
- attr.with_type(nil)
- else
- attr
- end
- end
- end
-
- def decode(coder)
- if coder["attributes"]
- coder["attributes"]
- else
- attributes_hash = Hash[coder["concise_attributes"].map do |attr|
- if attr.type.nil?
- attr = attr.with_type(default_types[attr.name])
- end
- [attr.name, attr]
- end]
- AttributeSet.new(attributes_hash)
- end
- end
-
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
- protected
-
- attr_reader :default_types
- end
- end
-end
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 9224d58928..0b7c9398a8 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "active_record/attribute/user_provided_default"
+require "active_model/attribute/user_provided_default"
module ActiveRecord
# See ActiveRecord::Attributes::ClassMethods for documentation
@@ -250,14 +250,14 @@ module ActiveRecord
if value == NO_DEFAULT_PROVIDED
default_attribute = _default_attributes[name].with_type(type)
elsif from_user
- default_attribute = Attribute::UserProvidedDefault.new(
+ default_attribute = ActiveModel::Attribute::UserProvidedDefault.new(
name,
value,
type,
_default_attributes.fetch(name.to_s) { nil },
)
else
- default_attribute = Attribute.from_database(name, value, type)
+ default_attribute = ActiveModel::Attribute.from_database(name, value, type)
end
_default_attributes[name] = default_attribute
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 147e16e9fa..d9ac8db6a8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -240,7 +240,7 @@ module ActiveRecord
rollback_transaction if transaction
else
begin
- commit_transaction
+ commit_transaction if transaction
rescue Exception
rollback_transaction(transaction) unless transaction.state.completed?
raise
diff --git a/activerecord/lib/active_record/legacy_yaml_adapter.rb b/activerecord/lib/active_record/legacy_yaml_adapter.rb
index 23644aab8f..ffa095dd94 100644
--- a/activerecord/lib/active_record/legacy_yaml_adapter.rb
+++ b/activerecord/lib/active_record/legacy_yaml_adapter.rb
@@ -8,7 +8,7 @@ module ActiveRecord
case coder["active_record_yaml_version"]
when 1, 2 then coder
else
- if coder["attributes"].is_a?(AttributeSet)
+ if coder["attributes"].is_a?(ActiveModel::AttributeSet)
Rails420.convert(klass, coder)
else
Rails41.convert(klass, coder)
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 1485687053..d12a979a7f 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1224,7 +1224,7 @@ module ActiveRecord
# Return true if a valid version is not provided.
def invalid_target?
- !target && @target_version && @target_version > 0
+ @target_version && @target_version != 0 && !target
end
def execute_migration_in_transaction(migration, direction)
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index bed9400f51..12ee4a4137 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -323,7 +323,7 @@ module ActiveRecord
end
def attributes_builder # :nodoc:
- @attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key) do |name|
+ @attributes_builder ||= ActiveModel::AttributeSet::Builder.new(attribute_types, primary_key) do |name|
unless columns_hash.key?(name)
_default_attributes[name].dup
end
@@ -346,7 +346,7 @@ module ActiveRecord
end
def yaml_encoder # :nodoc:
- @yaml_encoder ||= AttributeSet::YAMLEncoder.new(attribute_types)
+ @yaml_encoder ||= ActiveModel::AttributeSet::YAMLEncoder.new(attribute_types)
end
# Returns the type of the attribute with the given name, after applying
@@ -376,7 +376,7 @@ module ActiveRecord
end
def _default_attributes # :nodoc:
- @default_attributes ||= AttributeSet.new({})
+ @default_attributes ||= ActiveModel::AttributeSet.new({})
end
# Returns an array of column names as strings.
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 723272b4b2..3bca2982e0 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -97,16 +97,27 @@ db_namespace = namespace :db do
task up: [:environment, :load_config] do
raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
- version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
- ActiveRecord::Migrator.run(:up, ActiveRecord::Tasks::DatabaseTasks.migrations_paths, version)
+ ActiveRecord::Tasks::DatabaseTasks.check_target_version
+
+ ActiveRecord::Migrator.run(
+ :up,
+ ActiveRecord::Tasks::DatabaseTasks.migrations_paths,
+ ActiveRecord::Tasks::DatabaseTasks.target_version
+ )
db_namespace["_dump"].invoke
end
# desc 'Runs the "down" for a given migration VERSION.'
task down: [:environment, :load_config] do
raise "VERSION is required - To go down one migration, use db:rollback" if !ENV["VERSION"] || ENV["VERSION"].empty?
- version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
- ActiveRecord::Migrator.run(:down, ActiveRecord::Tasks::DatabaseTasks.migrations_paths, version)
+
+ ActiveRecord::Tasks::DatabaseTasks.check_target_version
+
+ ActiveRecord::Migrator.run(
+ :down,
+ ActiveRecord::Tasks::DatabaseTasks.migrations_paths,
+ ActiveRecord::Tasks::DatabaseTasks.target_version
+ )
db_namespace["_dump"].invoke
end
diff --git a/activerecord/lib/active_record/railties/jdbcmysql_error.rb b/activerecord/lib/active_record/railties/jdbcmysql_error.rb
deleted file mode 100644
index 72c75ddd52..0000000000
--- a/activerecord/lib/active_record/railties/jdbcmysql_error.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-#FIXME Remove if ArJdbcMysql will give.
-module ArJdbcMySQL #:nodoc:
- class Error < StandardError #:nodoc:
- attr_accessor :error_number, :sql_state
-
- def initialize(msg)
- super
- @error_number = nil
- @sql_state = nil
- end
-
- # Mysql gem compatibility
- alias_method :errno, :error_number
- alias_method :error, :message
- end
-end
diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb
index fad08e2613..3532f28858 100644
--- a/activerecord/lib/active_record/relation/query_attribute.rb
+++ b/activerecord/lib/active_record/relation/query_attribute.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require "active_record/attribute"
+require "active_model/attribute"
module ActiveRecord
class Relation
- class QueryAttribute < Attribute # :nodoc:
+ class QueryAttribute < ActiveModel::Attribute # :nodoc:
def type_cast(value)
value
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 897ff5c8af..9c5be4ad9b 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -930,7 +930,7 @@ module ActiveRecord
arel.where(where_clause.ast) unless where_clause.empty?
arel.having(having_clause.ast) unless having_clause.empty?
if limit_value
- limit_attribute = Attribute.with_cast_value(
+ limit_attribute = ActiveModel::Attribute.with_cast_value(
"LIMIT".freeze,
connection.sanitize_limit(limit_value),
Type.default_value,
@@ -938,7 +938,7 @@ module ActiveRecord
arel.take(Arel::Nodes::BindParam.new(limit_attribute))
end
if offset_value
- offset_attribute = Attribute.with_cast_value(
+ offset_attribute = ActiveModel::Attribute.with_cast_value(
"OFFSET".freeze,
offset_value.to_i,
Type.default_value,
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index ff388ff1f6..4657e51e6d 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -164,13 +164,12 @@ module ActiveRecord
end
def migrate
- raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty?
+ check_target_version
verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] != "false" : true
- version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
scope = ENV["SCOPE"]
verbose_was, Migration.verbose = Migration.verbose, verbose
- Migrator.migrate(migrations_paths, version) do |migration|
+ Migrator.migrate(migrations_paths, target_version) do |migration|
scope.blank? || scope == migration.scope
end
ActiveRecord::Base.clear_cache!
@@ -178,6 +177,16 @@ module ActiveRecord
Migration.verbose = verbose_was
end
+ def check_target_version
+ if target_version && !(Migration::MigrationFilenameRegexp.match?(ENV["VERSION"]) || /\A\d+\z/.match?(ENV["VERSION"]))
+ raise "Invalid format of target version: `VERSION=#{ENV['VERSION']}`"
+ end
+ end
+
+ def target_version
+ ENV["VERSION"].to_i if ENV["VERSION"] && !ENV["VERSION"].empty?
+ end
+
def charset_current(environment = env)
charset ActiveRecord::Base.configurations[environment]
end
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 2f91884f59..e697fa6def 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -3,8 +3,6 @@
module ActiveRecord
module Tasks # :nodoc:
class MySQLDatabaseTasks # :nodoc:
- ACCESS_DENIED_ERROR = 1045
-
delegate :connection, :establish_connection, to: ActiveRecord::Base
def initialize(configuration)
@@ -21,20 +19,6 @@ module ActiveRecord
else
raise
end
- rescue error_class => error
- if error.respond_to?(:errno) && error.errno == ACCESS_DENIED_ERROR
- $stdout.print error.message
- establish_connection root_configuration_without_database
- connection.create_database configuration["database"], creation_options
- if configuration["username"] != "root"
- connection.execute grant_statement.gsub(/\s+/, " ").strip
- end
- establish_connection configuration
- else
- $stderr.puts error.inspect
- $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}"
- $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration["encoding"]
- end
end
def drop
@@ -99,37 +83,6 @@ module ActiveRecord
end
end
- def error_class
- if configuration["adapter"].include?("jdbc")
- require "active_record/railties/jdbcmysql_error"
- ArJdbcMySQL::Error
- elsif defined?(Mysql2)
- Mysql2::Error
- else
- StandardError
- end
- end
-
- def grant_statement
- <<-SQL
-GRANT ALL PRIVILEGES ON `#{configuration['database']}`.*
- TO '#{configuration['username']}'@'localhost'
-IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
- SQL
- end
-
- def root_configuration_without_database
- configuration_without_database.merge(
- "username" => "root",
- "password" => root_password
- )
- end
-
- def root_password
- $stdout.print "Please provide the root password for your MySQL installation\n>"
- $stdin.gets.strip
- end
-
def prepare_command_options
args = {
"host" => "--host",