diff options
-rw-r--r-- | activerecord/lib/active_record/associations/preloader/association.rb | 12 | ||||
-rw-r--r-- | activerecord/lib/active_record/attribute_methods/dirty.rb | 99 | ||||
-rw-r--r-- | activerecord/lib/active_record/attribute_mutation_tracker.rb | 9 | ||||
-rw-r--r-- | activerecord/lib/active_record/attribute_set.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/mysql/quoting.rb | 8 | ||||
-rw-r--r-- | activerecord/lib/active_record/reflection.rb | 6 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation.rb | 4 | ||||
-rw-r--r-- | activerecord/test/cases/dirty_test.rb | 15 | ||||
-rw-r--r-- | activerecord/test/cases/quoting_test.rb | 14 | ||||
-rw-r--r-- | activerecord/test/cases/scoping/default_scoping_test.rb | 18 | ||||
-rw-r--r-- | activerecord/test/models/comment.rb | 1 | ||||
-rw-r--r-- | activerecord/test/models/parrot.rb | 2 | ||||
-rw-r--r-- | activerecord/test/schema/schema.rb | 1 |
13 files changed, 76 insertions, 115 deletions
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 85343040db..698fd29beb 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -114,8 +114,18 @@ module ActiveRecord @reflection_scope ||= reflection.scope_for(klass) end + def klass_scope + current_scope = klass.current_scope + + if current_scope && current_scope.empty_scope? + klass.unscoped + else + klass.default_scoped + end + end + def build_scope - scope = klass.default_scoped + scope = klass_scope if reflection.type scope.where!(reflection.type => model.base_class.sti_name) diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 07d194cc57..5efe051125 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -17,8 +17,8 @@ module ActiveRecord class_attribute :partial_writes, instance_writer: false, default: true - after_create { changes_internally_applied } - after_update { changes_internally_applied } + after_create { changes_applied } + after_update { changes_applied } # Attribute methods for "changed in last call to save?" attribute_method_affix(prefix: "saved_change_to_", suffix: "?") @@ -30,25 +30,10 @@ module ActiveRecord attribute_method_suffix("_change_to_be_saved", "_in_database") end - # Attempts to +save+ the record and clears changed attributes if successful. - def save(*) - if status = super - changes_applied - end - status - end - - # Attempts to <tt>save!</tt> the record and clears changed attributes if successful. - def save!(*) - super.tap do - changes_applied - end - end - # <tt>reload</tt> the record and clears changed attributes. def reload(*) super.tap do - @previous_mutation_tracker = nil + @mutations_before_last_save = nil clear_mutation_trackers @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new end @@ -62,20 +47,16 @@ module ActiveRecord clear_mutation_trackers end - def changes_internally_applied # :nodoc: + def changes_applied @mutations_before_last_save = mutation_tracker - forget_attribute_assignments @mutations_from_database = AttributeMutationTracker.new(@attributes) - end - - def changes_applied - @previous_mutation_tracker = mutation_tracker @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new + forget_attribute_assignments clear_mutation_trackers end def clear_changes_information - @previous_mutation_tracker = nil + @mutations_before_last_save = nil @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new forget_attribute_assignments clear_mutation_trackers @@ -100,28 +81,18 @@ module ActiveRecord if defined?(@cached_changed_attributes) @cached_changed_attributes else - emit_warning_if_needed("changed_attributes", "saved_changes.transform_values(&:first)") super.reverse_merge(mutation_tracker.changed_values).freeze end end def changes cache_changed_attributes do - emit_warning_if_needed("changes", "saved_changes") super end end def previous_changes - unless previous_mutation_tracker.equal?(mutations_before_last_save) - ActiveSupport::Deprecation.warn(<<-EOW.strip_heredoc) - The behavior of `previous_changes` inside of after callbacks is - deprecated without replacement. In the next release of Rails, - this method inside of `after_save` will return the changes that - were just saved. - EOW - end - previous_mutation_tracker.changes + mutations_before_last_save.changes end def attribute_changed_in_place?(attr_name) @@ -211,31 +182,6 @@ module ActiveRecord changes_to_save.transform_values(&:first) end - def attribute_was(*) - emit_warning_if_needed("attribute_was", "attribute_before_last_save") - super - end - - def attribute_change(*) - emit_warning_if_needed("attribute_change", "saved_change_to_attribute") - super - end - - def attribute_changed?(*) - emit_warning_if_needed("attribute_changed?", "saved_change_to_attribute?") - super - end - - def changed?(*) - emit_warning_if_needed("changed?", "saved_changes?") - super - end - - def changed(*) - emit_warning_if_needed("changed", "saved_changes.keys") - super - end - private def mutation_tracker @@ -245,18 +191,6 @@ module ActiveRecord @mutation_tracker ||= AttributeMutationTracker.new(@attributes) end - def emit_warning_if_needed(method_name, new_method_name) - unless mutation_tracker.equal?(mutations_from_database) - ActiveSupport::Deprecation.warn(<<-EOW.squish) - The behavior of `#{method_name}` inside of after callbacks will - be changing in the next version of Rails. The new return value will reflect the - behavior of calling the method after `save` returned (e.g. the opposite of what - it returns now). To maintain the current behavior, use `#{new_method_name}` - instead. - EOW - end - end - def mutations_from_database unless defined?(@mutations_from_database) @mutations_from_database = nil @@ -275,17 +209,7 @@ module ActiveRecord def attribute_will_change!(attr_name) super - if self.class.has_attribute?(attr_name) - mutations_from_database.force_change(attr_name) - else - ActiveSupport::Deprecation.warn(<<-EOW.squish) - #{attr_name} is not an attribute known to Active Record. - This behavior is deprecated and will be removed in the next - version of Rails. If you'd like #{attr_name} to be managed - by Active Record, add `attribute :#{attr_name}` to your class. - EOW - mutations_from_database.deprecated_force_change(attr_name) - end + mutations_from_database.force_change(attr_name) end def _update_record(*) @@ -307,15 +231,10 @@ module ActiveRecord def clear_mutation_trackers @mutation_tracker = nil @mutations_from_database = nil - @mutations_before_last_save = nil - end - - def previous_mutation_tracker - @previous_mutation_tracker ||= NullMutationTracker.instance end def mutations_before_last_save - @mutations_before_last_save ||= previous_mutation_tracker + @mutations_before_last_save ||= NullMutationTracker.instance end def cache_changed_attributes diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb index a01a58f8a5..e275ae72ba 100644 --- a/activerecord/lib/active_record/attribute_mutation_tracker.rb +++ b/activerecord/lib/active_record/attribute_mutation_tracker.rb @@ -5,7 +5,6 @@ module ActiveRecord def initialize(attributes) @attributes = attributes @forced_changes = Set.new - @deprecated_forced_changes = Set.new end def changed_values @@ -33,7 +32,7 @@ module ActiveRecord end def any_changes? - attr_names.any? { |attr| changed?(attr) } || deprecated_forced_changes.any? + attr_names.any? { |attr| changed?(attr) } end def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) @@ -62,15 +61,11 @@ module ActiveRecord forced_changes << attr_name.to_s end - def deprecated_force_change(attr_name) - deprecated_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, :deprecated_forced_changes + attr_reader :attributes, :forced_changes private diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb index 6399e3de70..a963ba015a 100644 --- a/activerecord/lib/active_record/attribute_set.rb +++ b/activerecord/lib/active_record/attribute_set.rb @@ -98,8 +98,6 @@ module ActiveRecord attributes == other.attributes 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 diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb index d4f5906b33..0cc0ac74fe 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb @@ -39,6 +39,14 @@ module ActiveRecord def quoted_binary(value) "x'#{value.hex}'" end + + def _type_cast(value) + case value + when Type::Time::Value then value.__getobj__ + when Date, Time then value + else super + end + end end end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 4f53280b5c..1026e20f79 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -219,8 +219,10 @@ module ActiveRecord end def klass_join_scope(table, predicate_builder) # :nodoc: - if klass.current_scope && klass.current_scope.values.empty? - klass.unscoped + current_scope = klass.current_scope + + if current_scope && current_scope.empty_scope? + build_scope(table, predicate_builder) else klass.default_scoped(build_scope(table, predicate_builder)) end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index c5d98e970a..d9e1e5c39c 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -642,6 +642,10 @@ module ActiveRecord "#<#{self.class.name} [#{entries.join(', ')}]>" end + def empty_scope? # :nodoc: + @values == klass.unscoped.values + end + protected def load_records(records) diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index f72e0d2ead..bb584b0c6f 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -299,11 +299,9 @@ class DirtyTest < ActiveRecord::TestCase end def test_virtual_attribute_will_change - assert_deprecated do - parrot = Parrot.create!(name: "Ruby") - parrot.send(:attribute_will_change!, :cancel_save_from_callback) - assert parrot.has_changes_to_save? - end + parrot = Parrot.create!(name: "Ruby") + parrot.send(:attribute_will_change!, :cancel_save_from_callback) + assert parrot.has_changes_to_save? end def test_association_assignment_changes_foreign_key @@ -839,15 +837,14 @@ class DirtyTest < ActiveRecord::TestCase assert_equal %w(first_name lock_version updated_at).sort, person.saved_changes.keys.sort end - test "changed? in after callbacks returns true but is deprecated" do + test "changed? in after callbacks returns false" do klass = Class.new(ActiveRecord::Base) do self.table_name = "people" after_save do - ActiveSupport::Deprecation.silence do - raise "changed? should be true" unless changed? - end + raise "changed? should be false" if changed? raise "has_changes_to_save? should be false" if has_changes_to_save? + raise "saved_changes? should be true" unless saved_changes? end end diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index b21adccc4b..98b20915c6 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -174,13 +174,21 @@ module ActiveRecord def test_type_cast_date date = Date.today - expected = @conn.quoted_date(date) + if current_adapter?(:Mysql2Adapter) + expected = date + else + expected = @conn.quoted_date(date) + end assert_equal expected, @conn.type_cast(date) end def test_type_cast_time time = Time.now - expected = @conn.quoted_date(time) + if current_adapter?(:Mysql2Adapter) + expected = time + else + expected = @conn.quoted_date(time) + end assert_equal expected, @conn.type_cast(time) end @@ -257,7 +265,7 @@ module ActiveRecord def test_type_cast_ar_object value = DatetimePrimaryKey.new(id: @time) - assert_equal "2017-02-14 12:34:56.789000", @connection.type_cast(value) + assert_equal @connection.type_cast(value.id), @connection.type_cast(value) end end end diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index a5061cfce7..a8c79628e7 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -392,6 +392,24 @@ class DefaultScopingTest < ActiveRecord::TestCase Comment.joins(:post).to_a end + def test_sti_association_with_unscoped_not_affected_by_default_scope + post = posts(:thinking) + comments = [comments(:does_it_hurt)] + + post.special_comments.update_all(deleted_at: Time.now) + + assert_raises(ActiveRecord::RecordNotFound) { Post.joins(:special_comments).find(post.id) } + assert_equal [], post.special_comments + + SpecialComment.unscoped do + assert_equal post, Post.joins(:special_comments).find(post.id) + assert_equal comments, Post.joins(:special_comments).find(post.id).special_comments + assert_equal comments, Post.eager_load(:special_comments).find(post.id).special_comments + assert_equal comments, Post.includes(:special_comments).find(post.id).special_comments + assert_equal comments, Post.preload(:special_comments).find(post.id).special_comments + end + end + def test_default_scope_select_ignored_by_aggregations assert_equal DeveloperWithSelect.all.to_a.count, DeveloperWithSelect.count end diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index eecf923046..dc2d421cd7 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -54,6 +54,7 @@ class Comment < ActiveRecord::Base end class SpecialComment < Comment + default_scope { where(deleted_at: nil) } end class SubSpecialComment < SpecialComment diff --git a/activerecord/test/models/parrot.rb b/activerecord/test/models/parrot.rb index 1e5f9285a8..255ac19365 100644 --- a/activerecord/test/models/parrot.rb +++ b/activerecord/test/models/parrot.rb @@ -8,7 +8,7 @@ class Parrot < ActiveRecord::Base validates_presence_of :name - attr_accessor :cancel_save_from_callback + attribute :cancel_save_from_callback before_save :cancel_save_callback_method, if: :cancel_save_from_callback def cancel_save_callback_method throw(:abort) diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index f534e9c00e..90185ff6c5 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -189,6 +189,7 @@ ActiveRecord::Schema.define do t.string :resource_id t.string :resource_type t.integer :developer_id + t.datetime :deleted_at end create_table :companies, force: true do |t| |