2017-07-09 13:41:28 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-08-06 12:26:20 -04:00
|
|
|
require "cases/helper"
|
|
|
|
require "models/topic" # For booleans
|
|
|
|
require "models/pirate" # For timestamps
|
|
|
|
require "models/parrot"
|
|
|
|
require "models/person" # For optimistic locking
|
|
|
|
require "models/aircraft"
|
2016-09-26 18:24:06 -04:00
|
|
|
require "models/numeric_data"
|
2009-01-04 17:49:37 -05:00
|
|
|
|
2008-03-30 21:10:04 -04:00
|
|
|
class DirtyTest < ActiveRecord::TestCase
|
2012-11-17 11:01:35 -05:00
|
|
|
include InTimeZone
|
2012-11-17 10:35:39 -05:00
|
|
|
|
2010-03-15 16:56:19 -04:00
|
|
|
# Dummy to force column loads so query counts are clean.
|
|
|
|
def setup
|
2016-08-06 13:37:57 -04:00
|
|
|
Person.create first_name: "foo"
|
2010-03-15 16:56:19 -04:00
|
|
|
end
|
|
|
|
|
2008-03-28 20:04:27 -04:00
|
|
|
def test_attribute_changes
|
|
|
|
# New record - no changes.
|
2008-03-29 18:19:26 -04:00
|
|
|
pirate = Pirate.new
|
2016-04-11 22:33:52 -04:00
|
|
|
assert_equal false, pirate.catchphrase_changed?
|
|
|
|
assert_equal false, pirate.non_validated_parrot_id_changed?
|
2008-03-28 20:04:27 -04:00
|
|
|
|
2008-03-29 18:19:26 -04:00
|
|
|
# Change catchphrase.
|
2016-08-06 12:26:20 -04:00
|
|
|
pirate.catchphrase = "arrr"
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :catchphrase_changed?
|
2008-03-29 18:19:26 -04:00
|
|
|
assert_nil pirate.catchphrase_was
|
2016-08-06 12:26:20 -04:00
|
|
|
assert_equal [nil, "arrr"], pirate.catchphrase_change
|
2008-03-28 20:04:27 -04:00
|
|
|
|
|
|
|
# Saved - no changes.
|
2008-03-29 18:19:26 -04:00
|
|
|
pirate.save!
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :catchphrase_changed?
|
2008-03-29 18:19:26 -04:00
|
|
|
assert_nil pirate.catchphrase_change
|
2008-03-28 20:04:27 -04:00
|
|
|
|
|
|
|
# Same value - no changes.
|
2016-08-06 12:26:20 -04:00
|
|
|
pirate.catchphrase = "arrr"
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :catchphrase_changed?
|
2008-03-29 18:19:26 -04:00
|
|
|
assert_nil pirate.catchphrase_change
|
2008-03-28 20:04:27 -04:00
|
|
|
end
|
|
|
|
|
2010-04-08 03:39:46 -04:00
|
|
|
def test_time_attributes_changes_with_time_zone
|
2016-08-06 12:26:20 -04:00
|
|
|
in_time_zone "Paris" do
|
2010-04-08 03:39:46 -04:00
|
|
|
target = Class.new(ActiveRecord::Base)
|
2016-08-06 12:26:20 -04:00
|
|
|
target.table_name = "pirates"
|
2010-04-08 03:39:46 -04:00
|
|
|
|
|
|
|
# New record - no changes.
|
|
|
|
pirate = target.new
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :created_on_changed?
|
2010-04-08 03:39:46 -04:00
|
|
|
assert_nil pirate.created_on_change
|
|
|
|
|
|
|
|
# Saved - no changes.
|
2016-08-06 12:26:20 -04:00
|
|
|
pirate.catchphrase = "arrrr, time zone!!"
|
2010-04-08 03:39:46 -04:00
|
|
|
pirate.save!
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :created_on_changed?
|
2010-04-08 03:39:46 -04:00
|
|
|
assert_nil pirate.created_on_change
|
|
|
|
|
|
|
|
# Change created_on.
|
|
|
|
old_created_on = pirate.created_on
|
|
|
|
pirate.created_on = Time.now - 1.day
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :created_on_changed?
|
2010-04-08 03:39:46 -04:00
|
|
|
assert_kind_of ActiveSupport::TimeWithZone, pirate.created_on_was
|
|
|
|
assert_equal old_created_on, pirate.created_on_was
|
2013-01-25 03:14:27 -05:00
|
|
|
pirate.created_on = old_created_on
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :created_on_changed?
|
2010-04-08 03:39:46 -04:00
|
|
|
end
|
|
|
|
end
|
2012-06-14 18:27:13 -04:00
|
|
|
|
2012-06-01 15:09:33 -04:00
|
|
|
def test_setting_time_attributes_with_time_zone_field_to_itself_should_not_be_marked_as_a_change
|
2016-08-06 12:26:20 -04:00
|
|
|
in_time_zone "Paris" do
|
2012-06-01 15:09:33 -04:00
|
|
|
target = Class.new(ActiveRecord::Base)
|
2016-08-06 12:26:20 -04:00
|
|
|
target.table_name = "pirates"
|
2012-06-01 15:09:33 -04:00
|
|
|
|
2015-09-24 17:01:41 -04:00
|
|
|
pirate = target.create!
|
2012-06-01 15:09:33 -04:00
|
|
|
pirate.created_on = pirate.created_on
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :created_on_changed?
|
2012-06-01 15:09:33 -04:00
|
|
|
end
|
|
|
|
end
|
2010-04-08 03:39:46 -04:00
|
|
|
|
|
|
|
def test_time_attributes_changes_without_time_zone_by_skip
|
2016-08-06 12:26:20 -04:00
|
|
|
in_time_zone "Paris" do
|
2010-04-08 03:39:46 -04:00
|
|
|
target = Class.new(ActiveRecord::Base)
|
2016-08-06 12:26:20 -04:00
|
|
|
target.table_name = "pirates"
|
2010-04-08 03:39:46 -04:00
|
|
|
|
|
|
|
target.skip_time_zone_conversion_for_attributes = [:created_on]
|
|
|
|
|
|
|
|
# New record - no changes.
|
|
|
|
pirate = target.new
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :created_on_changed?
|
2010-04-08 03:39:46 -04:00
|
|
|
assert_nil pirate.created_on_change
|
|
|
|
|
|
|
|
# Saved - no changes.
|
2016-08-06 12:26:20 -04:00
|
|
|
pirate.catchphrase = "arrrr, time zone!!"
|
2010-04-08 03:39:46 -04:00
|
|
|
pirate.save!
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :created_on_changed?
|
2010-04-08 03:39:46 -04:00
|
|
|
assert_nil pirate.created_on_change
|
|
|
|
|
|
|
|
# Change created_on.
|
|
|
|
old_created_on = pirate.created_on
|
|
|
|
pirate.created_on = Time.now + 1.day
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :created_on_changed?
|
2010-04-08 03:39:46 -04:00
|
|
|
# kind_of does not work because
|
|
|
|
# ActiveSupport::TimeWithZone.name == 'Time'
|
2010-05-18 21:47:24 -04:00
|
|
|
assert_instance_of Time, pirate.created_on_was
|
2010-04-08 03:39:46 -04:00
|
|
|
assert_equal old_created_on, pirate.created_on_was
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_time_attributes_changes_without_time_zone
|
2013-10-25 10:56:05 -04:00
|
|
|
with_timezone_config aware_attributes: false do
|
|
|
|
target = Class.new(ActiveRecord::Base)
|
2016-08-06 12:26:20 -04:00
|
|
|
target.table_name = "pirates"
|
2010-04-08 03:39:46 -04:00
|
|
|
|
2013-10-25 10:56:05 -04:00
|
|
|
# New record - no changes.
|
|
|
|
pirate = target.new
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :created_on_changed?
|
2013-10-25 10:56:05 -04:00
|
|
|
assert_nil pirate.created_on_change
|
2010-04-08 03:39:46 -04:00
|
|
|
|
2013-10-25 10:56:05 -04:00
|
|
|
# Saved - no changes.
|
2016-08-06 12:26:20 -04:00
|
|
|
pirate.catchphrase = "arrrr, time zone!!"
|
2013-10-25 10:56:05 -04:00
|
|
|
pirate.save!
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :created_on_changed?
|
2013-10-25 10:56:05 -04:00
|
|
|
assert_nil pirate.created_on_change
|
2010-04-08 03:39:46 -04:00
|
|
|
|
2013-10-25 10:56:05 -04:00
|
|
|
# Change created_on.
|
|
|
|
old_created_on = pirate.created_on
|
|
|
|
pirate.created_on = Time.now + 1.day
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :created_on_changed?
|
2013-10-25 10:56:05 -04:00
|
|
|
# kind_of does not work because
|
|
|
|
# ActiveSupport::TimeWithZone.name == 'Time'
|
|
|
|
assert_instance_of Time, pirate.created_on_was
|
|
|
|
assert_equal old_created_on, pirate.created_on_was
|
|
|
|
end
|
2010-04-08 03:39:46 -04:00
|
|
|
end
|
|
|
|
|
2008-08-15 21:49:22 -04:00
|
|
|
def test_aliased_attribute_changes
|
|
|
|
# the actual attribute here is name, title is an
|
|
|
|
# alias setup via alias_attribute
|
|
|
|
parrot = Parrot.new
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate parrot, :title_changed?
|
2008-08-15 21:49:22 -04:00
|
|
|
assert_nil parrot.title_change
|
|
|
|
|
2016-08-06 12:26:20 -04:00
|
|
|
parrot.name = "Sam"
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate parrot, :title_changed?
|
2008-08-15 21:49:22 -04:00
|
|
|
assert_nil parrot.title_was
|
|
|
|
assert_equal parrot.name_change, parrot.title_change
|
|
|
|
end
|
|
|
|
|
2014-07-15 15:12:23 -04:00
|
|
|
def test_restore_attribute!
|
2016-08-06 13:37:57 -04:00
|
|
|
pirate = Pirate.create!(catchphrase: "Yar!")
|
2016-08-06 12:26:20 -04:00
|
|
|
pirate.catchphrase = "Ahoy!"
|
2014-07-15 15:12:23 -04:00
|
|
|
|
|
|
|
pirate.restore_catchphrase!
|
2009-08-04 17:23:08 -04:00
|
|
|
assert_equal "Yar!", pirate.catchphrase
|
|
|
|
assert_equal Hash.new, pirate.changes
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :catchphrase_changed?
|
2009-08-04 17:23:08 -04:00
|
|
|
end
|
|
|
|
|
2009-01-04 17:22:53 -05:00
|
|
|
def test_nullable_number_not_marked_as_changed_if_new_value_is_blank
|
2008-05-20 15:50:46 -04:00
|
|
|
pirate = Pirate.new
|
|
|
|
|
|
|
|
["", nil].each do |value|
|
|
|
|
pirate.parrot_id = value
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :parrot_id_changed?
|
2008-05-20 15:50:46 -04:00
|
|
|
assert_nil pirate.parrot_id_change
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2009-01-04 17:49:37 -05:00
|
|
|
def test_nullable_decimal_not_marked_as_changed_if_new_value_is_blank
|
|
|
|
numeric_data = NumericData.new
|
|
|
|
|
|
|
|
["", nil].each do |value|
|
|
|
|
numeric_data.bank_balance = value
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate numeric_data, :bank_balance_changed?
|
2009-01-04 17:49:37 -05:00
|
|
|
assert_nil numeric_data.bank_balance_change
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_nullable_float_not_marked_as_changed_if_new_value_is_blank
|
|
|
|
numeric_data = NumericData.new
|
|
|
|
|
|
|
|
["", nil].each do |value|
|
|
|
|
numeric_data.temperature = value
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate numeric_data, :temperature_changed?
|
2009-01-04 17:49:37 -05:00
|
|
|
assert_nil numeric_data.temperature_change
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-11-25 05:13:39 -05:00
|
|
|
def test_nullable_datetime_not_marked_as_changed_if_new_value_is_blank
|
2016-08-06 12:26:20 -04:00
|
|
|
in_time_zone "Edinburgh" do
|
2012-11-25 05:13:39 -05:00
|
|
|
target = Class.new(ActiveRecord::Base)
|
2016-08-06 12:26:20 -04:00
|
|
|
target.table_name = "topics"
|
2012-11-25 05:13:39 -05:00
|
|
|
|
|
|
|
topic = target.create
|
2012-11-28 08:06:19 -05:00
|
|
|
assert_nil topic.written_on
|
2012-11-25 05:13:39 -05:00
|
|
|
|
2013-04-22 03:46:57 -04:00
|
|
|
["", nil].each do |value|
|
|
|
|
topic.written_on = value
|
|
|
|
assert_nil topic.written_on
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate topic, :written_on_changed?
|
2013-04-22 03:46:57 -04:00
|
|
|
end
|
2012-11-25 05:13:39 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-08-02 13:01:44 -04:00
|
|
|
def test_integer_zero_to_string_zero_not_marked_as_changed
|
2008-12-08 09:11:55 -05:00
|
|
|
pirate = Pirate.new
|
|
|
|
pirate.parrot_id = 0
|
2016-08-06 12:26:20 -04:00
|
|
|
pirate.catchphrase = "arrr"
|
2008-12-08 09:11:55 -05:00
|
|
|
assert pirate.save!
|
|
|
|
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :changed?
|
2008-12-08 09:11:55 -05:00
|
|
|
|
2016-08-06 12:26:20 -04:00
|
|
|
pirate.parrot_id = "0"
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :changed?
|
2008-12-08 09:11:55 -05:00
|
|
|
end
|
|
|
|
|
2012-08-02 13:01:44 -04:00
|
|
|
def test_integer_zero_to_integer_zero_not_marked_as_changed
|
|
|
|
pirate = Pirate.new
|
|
|
|
pirate.parrot_id = 0
|
2016-08-06 12:26:20 -04:00
|
|
|
pirate.catchphrase = "arrr"
|
2012-08-02 13:01:44 -04:00
|
|
|
assert pirate.save!
|
|
|
|
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :changed?
|
2012-08-02 13:01:44 -04:00
|
|
|
|
|
|
|
pirate.parrot_id = 0
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :changed?
|
2012-08-02 13:01:44 -04:00
|
|
|
end
|
|
|
|
|
2013-01-23 04:57:05 -05:00
|
|
|
def test_float_zero_to_string_zero_not_marked_as_changed
|
2016-08-06 13:37:57 -04:00
|
|
|
data = NumericData.new temperature: 0.0
|
2013-01-23 04:57:05 -05:00
|
|
|
data.save!
|
|
|
|
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate data, :changed?
|
2013-01-23 04:57:05 -05:00
|
|
|
|
2016-08-06 12:26:20 -04:00
|
|
|
data.temperature = "0"
|
2013-01-23 04:57:05 -05:00
|
|
|
assert_empty data.changes
|
|
|
|
|
2016-08-06 12:26:20 -04:00
|
|
|
data.temperature = "0.0"
|
2013-01-23 04:57:05 -05:00
|
|
|
assert_empty data.changes
|
|
|
|
|
2016-08-06 12:26:20 -04:00
|
|
|
data.temperature = "0.00"
|
2013-01-23 04:57:05 -05:00
|
|
|
assert_empty data.changes
|
|
|
|
end
|
2012-08-02 13:01:44 -04:00
|
|
|
|
2008-06-17 08:54:03 -04:00
|
|
|
def test_zero_to_blank_marked_as_changed
|
|
|
|
pirate = Pirate.new
|
|
|
|
pirate.catchphrase = "Yarrrr, me hearties"
|
|
|
|
pirate.parrot_id = 1
|
|
|
|
pirate.save
|
|
|
|
|
|
|
|
# check the change from 1 to ''
|
2013-01-18 09:15:19 -05:00
|
|
|
pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties")
|
2016-08-06 12:26:20 -04:00
|
|
|
pirate.parrot_id = ""
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :parrot_id_changed?
|
2008-06-17 08:54:03 -04:00
|
|
|
assert_equal([1, nil], pirate.parrot_id_change)
|
|
|
|
pirate.save
|
|
|
|
|
|
|
|
# check the change from nil to 0
|
2013-01-18 09:15:19 -05:00
|
|
|
pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties")
|
2008-06-17 08:54:03 -04:00
|
|
|
pirate.parrot_id = 0
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :parrot_id_changed?
|
2008-06-17 08:54:03 -04:00
|
|
|
assert_equal([nil, 0], pirate.parrot_id_change)
|
|
|
|
pirate.save
|
|
|
|
|
|
|
|
# check the change from 0 to ''
|
2013-01-18 09:15:19 -05:00
|
|
|
pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties")
|
2016-08-06 12:26:20 -04:00
|
|
|
pirate.parrot_id = ""
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :parrot_id_changed?
|
2008-06-17 08:54:03 -04:00
|
|
|
assert_equal([0, nil], pirate.parrot_id_change)
|
|
|
|
end
|
|
|
|
|
2008-03-28 20:04:27 -04:00
|
|
|
def test_object_should_be_changed_if_any_attribute_is_changed
|
2008-03-29 18:19:26 -04:00
|
|
|
pirate = Pirate.new
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :changed?
|
2008-03-29 18:19:26 -04:00
|
|
|
assert_equal [], pirate.changed
|
|
|
|
assert_equal Hash.new, pirate.changes
|
|
|
|
|
2016-08-06 12:26:20 -04:00
|
|
|
pirate.catchphrase = "arrr"
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :changed?
|
2008-03-29 18:19:26 -04:00
|
|
|
assert_nil pirate.catchphrase_was
|
|
|
|
assert_equal %w(catchphrase), pirate.changed
|
2016-08-16 03:30:11 -04:00
|
|
|
assert_equal({ "catchphrase" => [nil, "arrr"] }, pirate.changes)
|
2008-03-29 18:19:26 -04:00
|
|
|
|
|
|
|
pirate.save
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :changed?
|
2008-03-29 18:19:26 -04:00
|
|
|
assert_equal [], pirate.changed
|
|
|
|
assert_equal Hash.new, pirate.changes
|
|
|
|
end
|
2008-03-28 20:04:27 -04:00
|
|
|
|
2008-03-30 21:10:04 -04:00
|
|
|
def test_attribute_will_change!
|
2016-08-06 13:37:57 -04:00
|
|
|
pirate = Pirate.create!(catchphrase: "arr")
|
2008-03-30 21:10:04 -04:00
|
|
|
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :catchphrase_changed?
|
2008-03-30 21:10:04 -04:00
|
|
|
assert pirate.catchphrase_will_change!
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :catchphrase_changed?
|
2016-08-06 12:26:20 -04:00
|
|
|
assert_equal ["arr", "arr"], pirate.catchphrase_change
|
2008-03-30 21:10:04 -04:00
|
|
|
|
2016-08-06 12:26:20 -04:00
|
|
|
pirate.catchphrase << " matey!"
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :catchphrase_changed?
|
2016-08-06 12:26:20 -04:00
|
|
|
assert_equal ["arr", "arr matey!"], pirate.catchphrase_change
|
2008-03-30 21:10:04 -04:00
|
|
|
end
|
|
|
|
|
2017-02-11 17:46:28 -05:00
|
|
|
def test_virtual_attribute_will_change
|
2017-07-18 10:37:31 -04:00
|
|
|
parrot = Parrot.create!(name: "Ruby")
|
|
|
|
parrot.send(:attribute_will_change!, :cancel_save_from_callback)
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate parrot, :has_changes_to_save?
|
2017-02-11 17:46:28 -05:00
|
|
|
end
|
|
|
|
|
2008-03-30 21:10:04 -04:00
|
|
|
def test_association_assignment_changes_foreign_key
|
2016-08-06 13:37:57 -04:00
|
|
|
pirate = Pirate.create!(catchphrase: "jarl")
|
|
|
|
pirate.parrot = Parrot.create!(name: "Lorre")
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :changed?
|
2008-03-30 21:10:04 -04:00
|
|
|
assert_equal %w(parrot_id), pirate.changed
|
|
|
|
end
|
|
|
|
|
2008-03-29 18:19:26 -04:00
|
|
|
def test_attribute_should_be_compared_with_type_cast
|
|
|
|
topic = Topic.new
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate topic, :approved?
|
|
|
|
assert_not_predicate topic, :approved_changed?
|
2008-03-29 18:19:26 -04:00
|
|
|
|
|
|
|
# Coming from web form.
|
2016-08-16 03:30:11 -04:00
|
|
|
params = { topic: { approved: 1 } }
|
2008-03-29 18:19:26 -04:00
|
|
|
# In the controller.
|
|
|
|
topic.attributes = params[:topic]
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate topic, :approved?
|
|
|
|
assert_not_predicate topic, :approved_changed?
|
2008-03-28 20:04:27 -04:00
|
|
|
end
|
2008-03-30 21:10:04 -04:00
|
|
|
|
|
|
|
def test_partial_update
|
2016-08-06 13:37:57 -04:00
|
|
|
pirate = Pirate.new(catchphrase: "foo")
|
2008-03-30 21:49:57 -04:00
|
|
|
old_updated_on = 1.hour.ago.beginning_of_day
|
2008-03-30 21:10:04 -04:00
|
|
|
|
2012-10-19 11:45:41 -04:00
|
|
|
with_partial_writes Pirate, false do
|
2008-03-30 21:10:04 -04:00
|
|
|
assert_queries(2) { 2.times { pirate.save! } }
|
2016-08-06 13:37:57 -04:00
|
|
|
Pirate.where(id: pirate.id).update_all(updated_on: old_updated_on)
|
2008-03-30 21:10:04 -04:00
|
|
|
end
|
|
|
|
|
2012-10-19 11:45:41 -04:00
|
|
|
with_partial_writes Pirate, true do
|
2018-10-04 14:07:12 -04:00
|
|
|
assert_no_queries { 2.times { pirate.save! } }
|
2008-03-30 21:49:57 -04:00
|
|
|
assert_equal old_updated_on, pirate.reload.updated_on
|
|
|
|
|
2016-08-06 12:26:20 -04:00
|
|
|
assert_queries(1) { pirate.catchphrase = "bar"; pirate.save! }
|
2008-03-30 21:49:57 -04:00
|
|
|
assert_not_equal old_updated_on, pirate.reload.updated_on
|
2008-03-30 21:10:04 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2008-06-15 16:25:59 -04:00
|
|
|
def test_partial_update_with_optimistic_locking
|
2016-08-06 13:37:57 -04:00
|
|
|
person = Person.new(first_name: "foo")
|
2008-06-15 16:25:59 -04:00
|
|
|
|
2012-10-19 11:45:41 -04:00
|
|
|
with_partial_writes Person, false do
|
2008-06-15 16:25:59 -04:00
|
|
|
assert_queries(2) { 2.times { person.save! } }
|
2016-08-06 13:37:57 -04:00
|
|
|
Person.where(id: person.id).update_all(first_name: "baz")
|
2008-06-15 16:25:59 -04:00
|
|
|
end
|
|
|
|
|
2019-02-21 07:09:05 -05:00
|
|
|
old_lock_version = person.lock_version + 1
|
2017-03-06 17:09:55 -05:00
|
|
|
|
2012-10-19 11:45:41 -04:00
|
|
|
with_partial_writes Person, true do
|
2018-10-04 14:07:12 -04:00
|
|
|
assert_no_queries { 2.times { person.save! } }
|
2008-06-15 16:25:59 -04:00
|
|
|
assert_equal old_lock_version, person.reload.lock_version
|
|
|
|
|
2016-08-06 12:26:20 -04:00
|
|
|
assert_queries(1) { person.first_name = "bar"; person.save! }
|
2008-06-15 16:25:59 -04:00
|
|
|
assert_not_equal old_lock_version, person.reload.lock_version
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2008-05-12 18:46:57 -04:00
|
|
|
def test_changed_attributes_should_be_preserved_if_save_failure
|
|
|
|
pirate = Pirate.new
|
|
|
|
pirate.parrot_id = 1
|
2018-04-17 18:21:34 -04:00
|
|
|
assert_not pirate.save
|
2008-05-12 18:46:57 -04:00
|
|
|
check_pirate_after_save_failure(pirate)
|
|
|
|
|
|
|
|
pirate = Pirate.new
|
|
|
|
pirate.parrot_id = 1
|
2009-03-08 16:11:58 -04:00
|
|
|
assert_raise(ActiveRecord::RecordInvalid) { pirate.save! }
|
2008-05-12 18:46:57 -04:00
|
|
|
check_pirate_after_save_failure(pirate)
|
|
|
|
end
|
|
|
|
|
2008-05-21 11:27:20 -04:00
|
|
|
def test_reload_should_clear_changed_attributes
|
2016-08-06 13:37:57 -04:00
|
|
|
pirate = Pirate.create!(catchphrase: "shiver me timbers")
|
2008-05-21 11:27:20 -04:00
|
|
|
pirate.catchphrase = "*hic*"
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :changed?
|
2008-05-21 11:27:20 -04:00
|
|
|
pirate.reload
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :changed?
|
2008-05-21 11:27:20 -04:00
|
|
|
end
|
|
|
|
|
2010-11-23 14:57:33 -05:00
|
|
|
def test_dup_objects_should_not_copy_dirty_flag_from_creator
|
2016-08-06 13:37:57 -04:00
|
|
|
pirate = Pirate.create!(catchphrase: "shiver me timbers")
|
2010-11-23 14:57:33 -05:00
|
|
|
pirate_dup = pirate.dup
|
2014-07-15 15:12:23 -04:00
|
|
|
pirate_dup.restore_catchphrase!
|
2010-05-16 06:24:41 -04:00
|
|
|
pirate.catchphrase = "I love Rum"
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :catchphrase_changed?
|
|
|
|
assert_not_predicate pirate_dup, :catchphrase_changed?
|
2010-05-16 06:24:41 -04:00
|
|
|
end
|
|
|
|
|
2008-08-11 09:12:53 -04:00
|
|
|
def test_reverted_changes_are_not_dirty
|
|
|
|
phrase = "shiver me timbers"
|
2016-08-06 13:37:57 -04:00
|
|
|
pirate = Pirate.create!(catchphrase: phrase)
|
2008-08-11 09:12:53 -04:00
|
|
|
pirate.catchphrase = "*hic*"
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :changed?
|
2008-08-11 09:12:53 -04:00
|
|
|
pirate.catchphrase = phrase
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :changed?
|
2008-08-11 09:12:53 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_reverted_changes_are_not_dirty_after_multiple_changes
|
|
|
|
phrase = "shiver me timbers"
|
2016-08-06 13:37:57 -04:00
|
|
|
pirate = Pirate.create!(catchphrase: phrase)
|
2008-08-11 09:12:53 -04:00
|
|
|
10.times do |i|
|
|
|
|
pirate.catchphrase = "*hic*" * i
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :changed?
|
2008-08-11 09:12:53 -04:00
|
|
|
end
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :changed?
|
2008-08-11 09:12:53 -04:00
|
|
|
pirate.catchphrase = phrase
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :changed?
|
2008-08-11 09:12:53 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_reverted_changes_are_not_dirty_going_from_nil_to_value_and_back
|
2016-08-06 13:37:57 -04:00
|
|
|
pirate = Pirate.create!(catchphrase: "Yar!")
|
2008-08-11 09:12:53 -04:00
|
|
|
|
|
|
|
pirate.parrot_id = 1
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :changed?
|
|
|
|
assert_predicate pirate, :parrot_id_changed?
|
|
|
|
assert_not_predicate pirate, :catchphrase_changed?
|
2008-08-11 09:12:53 -04:00
|
|
|
|
|
|
|
pirate.parrot_id = nil
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :changed?
|
|
|
|
assert_not_predicate pirate, :parrot_id_changed?
|
|
|
|
assert_not_predicate pirate, :catchphrase_changed?
|
2008-08-11 09:12:53 -04:00
|
|
|
end
|
|
|
|
|
2012-10-19 11:45:41 -04:00
|
|
|
def test_save_should_store_serialized_attributes_even_with_partial_writes
|
|
|
|
with_partial_writes(Topic) do
|
2016-08-16 03:30:11 -04:00
|
|
|
topic = Topic.create!(content: { a: "a" })
|
2014-06-12 17:13:28 -04:00
|
|
|
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate topic, :changed?
|
2014-06-12 17:13:28 -04:00
|
|
|
|
2008-08-11 13:16:58 -04:00
|
|
|
topic.content[:b] = "b"
|
2014-06-12 17:13:28 -04:00
|
|
|
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate topic, :changed?
|
2014-06-12 17:13:28 -04:00
|
|
|
|
2008-08-11 13:16:58 -04:00
|
|
|
topic.save!
|
2014-06-12 17:13:28 -04:00
|
|
|
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate topic, :changed?
|
2008-08-11 13:16:58 -04:00
|
|
|
assert_equal "b", topic.content[:b]
|
2014-06-12 17:13:28 -04:00
|
|
|
|
2008-08-11 13:16:58 -04:00
|
|
|
topic.reload
|
2014-06-12 17:13:28 -04:00
|
|
|
|
2008-08-11 13:16:58 -04:00
|
|
|
assert_equal "b", topic.content[:b]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-11-01 21:42:47 -04:00
|
|
|
def test_save_always_should_update_timestamps_when_serialized_attributes_are_present
|
2012-10-19 11:45:41 -04:00
|
|
|
with_partial_writes(Topic) do
|
2016-08-16 03:30:11 -04:00
|
|
|
topic = Topic.create!(content: { a: "a" })
|
2010-11-01 21:42:47 -04:00
|
|
|
topic.save!
|
|
|
|
|
|
|
|
updated_at = topic.updated_at
|
2015-09-23 11:33:43 -04:00
|
|
|
travel(1.second) do
|
2016-08-06 12:26:20 -04:00
|
|
|
topic.content[:hello] = "world"
|
2015-09-23 11:33:43 -04:00
|
|
|
topic.save!
|
|
|
|
end
|
2010-11-01 21:42:47 -04:00
|
|
|
|
|
|
|
assert_not_equal updated_at, topic.updated_at
|
2016-08-06 12:26:20 -04:00
|
|
|
assert_equal "world", topic.content[:hello]
|
2010-11-01 21:42:47 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-10-19 11:45:41 -04:00
|
|
|
def test_save_should_not_save_serialized_attribute_with_partial_writes_if_not_present
|
|
|
|
with_partial_writes(Topic) do
|
2017-08-09 15:06:49 -04:00
|
|
|
topic = Topic.create!(author_name: "Bill", content: { a: "a" })
|
|
|
|
topic = Topic.select("id, author_name").find(topic.id)
|
2016-08-06 12:26:20 -04:00
|
|
|
topic.update_columns author_name: "John"
|
2017-08-09 15:06:49 -04:00
|
|
|
assert_not_nil topic.reload.content
|
2009-08-09 11:34:05 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-04-08 18:06:48 -04:00
|
|
|
def test_changes_to_save_should_not_mutate_array_of_hashes
|
|
|
|
topic = Topic.new(author_name: "Bill", content: [{ a: "a" }])
|
|
|
|
|
|
|
|
topic.changes_to_save
|
|
|
|
|
|
|
|
assert_equal [{ a: "a" }], topic.content
|
|
|
|
end
|
|
|
|
|
2009-08-31 13:50:27 -04:00
|
|
|
def test_previous_changes
|
|
|
|
# original values should be in previous_changes
|
|
|
|
pirate = Pirate.new
|
2010-08-14 01:13:00 -04:00
|
|
|
|
2009-08-31 13:50:27 -04:00
|
|
|
assert_equal Hash.new, pirate.previous_changes
|
|
|
|
pirate.catchphrase = "arrr"
|
|
|
|
pirate.save!
|
2010-08-14 01:13:00 -04:00
|
|
|
|
2009-08-31 13:50:27 -04:00
|
|
|
assert_equal 4, pirate.previous_changes.size
|
2016-08-06 12:26:20 -04:00
|
|
|
assert_equal [nil, "arrr"], pirate.previous_changes["catchphrase"]
|
|
|
|
assert_equal [nil, pirate.id], pirate.previous_changes["id"]
|
|
|
|
assert_nil pirate.previous_changes["updated_on"][0]
|
|
|
|
assert_not_nil pirate.previous_changes["updated_on"][1]
|
|
|
|
assert_nil pirate.previous_changes["created_on"][0]
|
|
|
|
assert_not_nil pirate.previous_changes["created_on"][1]
|
2018-04-17 18:21:34 -04:00
|
|
|
assert_not pirate.previous_changes.key?("parrot_id")
|
2010-08-14 01:13:00 -04:00
|
|
|
|
2009-08-31 13:50:27 -04:00
|
|
|
# original values should be in previous_changes
|
|
|
|
pirate = Pirate.new
|
2010-08-14 01:13:00 -04:00
|
|
|
|
2009-08-31 13:50:27 -04:00
|
|
|
assert_equal Hash.new, pirate.previous_changes
|
|
|
|
pirate.catchphrase = "arrr"
|
|
|
|
pirate.save
|
2010-08-14 01:13:00 -04:00
|
|
|
|
2009-08-31 13:50:27 -04:00
|
|
|
assert_equal 4, pirate.previous_changes.size
|
2016-08-06 12:26:20 -04:00
|
|
|
assert_equal [nil, "arrr"], pirate.previous_changes["catchphrase"]
|
|
|
|
assert_equal [nil, pirate.id], pirate.previous_changes["id"]
|
2016-09-16 12:44:05 -04:00
|
|
|
assert_includes pirate.previous_changes, "updated_on"
|
|
|
|
assert_includes pirate.previous_changes, "created_on"
|
2018-04-17 18:21:34 -04:00
|
|
|
assert_not pirate.previous_changes.key?("parrot_id")
|
2010-08-14 01:13:00 -04:00
|
|
|
|
2009-08-31 13:50:27 -04:00
|
|
|
pirate.catchphrase = "Yar!!"
|
|
|
|
pirate.reload
|
|
|
|
assert_equal Hash.new, pirate.previous_changes
|
|
|
|
|
2013-01-18 09:15:19 -05:00
|
|
|
pirate = Pirate.find_by_catchphrase("arrr")
|
2015-09-23 11:33:43 -04:00
|
|
|
|
|
|
|
travel(1.second)
|
|
|
|
|
2009-08-31 13:50:27 -04:00
|
|
|
pirate.catchphrase = "Me Maties!"
|
|
|
|
pirate.save!
|
|
|
|
|
|
|
|
assert_equal 2, pirate.previous_changes.size
|
2016-08-06 12:26:20 -04:00
|
|
|
assert_equal ["arrr", "Me Maties!"], pirate.previous_changes["catchphrase"]
|
|
|
|
assert_not_nil pirate.previous_changes["updated_on"][0]
|
|
|
|
assert_not_nil pirate.previous_changes["updated_on"][1]
|
2018-04-17 18:21:34 -04:00
|
|
|
assert_not pirate.previous_changes.key?("parrot_id")
|
|
|
|
assert_not pirate.previous_changes.key?("created_on")
|
2009-08-31 13:50:27 -04:00
|
|
|
|
2013-01-18 09:15:19 -05:00
|
|
|
pirate = Pirate.find_by_catchphrase("Me Maties!")
|
2015-09-23 11:33:43 -04:00
|
|
|
|
|
|
|
travel(1.second)
|
|
|
|
|
2009-08-31 13:50:27 -04:00
|
|
|
pirate.catchphrase = "Thar She Blows!"
|
|
|
|
pirate.save
|
|
|
|
|
|
|
|
assert_equal 2, pirate.previous_changes.size
|
2016-08-06 12:26:20 -04:00
|
|
|
assert_equal ["Me Maties!", "Thar She Blows!"], pirate.previous_changes["catchphrase"]
|
|
|
|
assert_not_nil pirate.previous_changes["updated_on"][0]
|
|
|
|
assert_not_nil pirate.previous_changes["updated_on"][1]
|
2018-04-17 18:21:34 -04:00
|
|
|
assert_not pirate.previous_changes.key?("parrot_id")
|
|
|
|
assert_not pirate.previous_changes.key?("created_on")
|
2009-08-31 13:50:27 -04:00
|
|
|
|
2015-09-23 11:33:43 -04:00
|
|
|
travel(1.second)
|
|
|
|
|
2013-01-18 09:15:19 -05:00
|
|
|
pirate = Pirate.find_by_catchphrase("Thar She Blows!")
|
2013-01-02 11:46:58 -05:00
|
|
|
pirate.update(catchphrase: "Ahoy!")
|
2009-08-31 13:50:27 -04:00
|
|
|
|
|
|
|
assert_equal 2, pirate.previous_changes.size
|
2016-08-06 12:26:20 -04:00
|
|
|
assert_equal ["Thar She Blows!", "Ahoy!"], pirate.previous_changes["catchphrase"]
|
|
|
|
assert_not_nil pirate.previous_changes["updated_on"][0]
|
|
|
|
assert_not_nil pirate.previous_changes["updated_on"][1]
|
2018-04-17 18:21:34 -04:00
|
|
|
assert_not pirate.previous_changes.key?("parrot_id")
|
|
|
|
assert_not pirate.previous_changes.key?("created_on")
|
2012-08-25 21:54:55 -04:00
|
|
|
|
2015-09-23 11:33:43 -04:00
|
|
|
travel(1.second)
|
|
|
|
|
2013-01-18 09:15:19 -05:00
|
|
|
pirate = Pirate.find_by_catchphrase("Ahoy!")
|
2012-08-25 21:54:55 -04:00
|
|
|
pirate.update_attribute(:catchphrase, "Ninjas suck!")
|
|
|
|
|
|
|
|
assert_equal 2, pirate.previous_changes.size
|
2016-08-06 12:26:20 -04:00
|
|
|
assert_equal ["Ahoy!", "Ninjas suck!"], pirate.previous_changes["catchphrase"]
|
|
|
|
assert_not_nil pirate.previous_changes["updated_on"][0]
|
|
|
|
assert_not_nil pirate.previous_changes["updated_on"][1]
|
2018-04-17 18:21:34 -04:00
|
|
|
assert_not pirate.previous_changes.key?("parrot_id")
|
|
|
|
assert_not pirate.previous_changes.key?("created_on")
|
2009-08-31 13:50:27 -04:00
|
|
|
end
|
|
|
|
|
2017-02-25 11:45:04 -05:00
|
|
|
class Testings < ActiveRecord::Base; end
|
|
|
|
def test_field_named_field
|
|
|
|
ActiveRecord::Base.connection.create_table :testings do |t|
|
|
|
|
t.string :field
|
2011-11-07 20:32:49 -05:00
|
|
|
end
|
2017-02-25 11:45:04 -05:00
|
|
|
assert_nothing_raised do
|
|
|
|
Testings.new.attributes
|
|
|
|
end
|
|
|
|
ensure
|
|
|
|
ActiveRecord::Base.connection.drop_table :testings rescue nil
|
|
|
|
ActiveRecord::Base.clear_cache!
|
2011-11-07 20:32:49 -05:00
|
|
|
end
|
|
|
|
|
2013-01-22 08:16:00 -05:00
|
|
|
def test_datetime_attribute_can_be_updated_with_fractional_seconds
|
2015-09-24 17:27:59 -04:00
|
|
|
skip "Fractional seconds are not supported" unless subsecond_precision_supported?
|
2016-08-06 12:26:20 -04:00
|
|
|
in_time_zone "Paris" do
|
2013-01-22 08:16:00 -05:00
|
|
|
target = Class.new(ActiveRecord::Base)
|
2016-08-06 12:26:20 -04:00
|
|
|
target.table_name = "topics"
|
2013-01-22 08:16:00 -05:00
|
|
|
|
2016-08-06 12:26:20 -04:00
|
|
|
written_on = Time.utc(2012, 12, 1, 12, 0, 0).in_time_zone("Paris")
|
2013-01-22 08:16:00 -05:00
|
|
|
|
2016-08-06 13:37:57 -04:00
|
|
|
topic = target.create(written_on: written_on)
|
2013-01-22 08:16:00 -05:00
|
|
|
topic.written_on += 0.3
|
|
|
|
|
2016-08-06 12:26:20 -04:00
|
|
|
assert topic.written_on_changed?, "Fractional second update not detected"
|
2013-01-22 08:16:00 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-12-05 18:00:19 -05:00
|
|
|
def test_datetime_attribute_doesnt_change_if_zone_is_modified_in_string
|
2016-08-06 12:26:20 -04:00
|
|
|
time_in_paris = Time.utc(2014, 1, 1, 12, 0, 0).in_time_zone("Paris")
|
2016-08-06 13:37:57 -04:00
|
|
|
pirate = Pirate.create!(catchphrase: "rrrr", created_on: time_in_paris)
|
2013-12-05 18:00:19 -05:00
|
|
|
|
2016-08-06 12:26:20 -04:00
|
|
|
pirate.created_on = pirate.created_on.in_time_zone("Tokyo").to_s
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :created_on_changed?
|
2013-12-05 18:00:19 -05:00
|
|
|
end
|
|
|
|
|
2012-09-28 12:55:35 -04:00
|
|
|
test "partial insert" do
|
2012-10-19 11:45:41 -04:00
|
|
|
with_partial_writes Person do
|
2012-09-28 12:55:35 -04:00
|
|
|
jon = nil
|
2012-09-28 15:57:27 -04:00
|
|
|
assert_sql(/first_name/i) do
|
2016-08-06 12:26:20 -04:00
|
|
|
jon = Person.create! first_name: "Jon"
|
2012-09-28 12:55:35 -04:00
|
|
|
end
|
|
|
|
|
2016-08-06 12:26:20 -04:00
|
|
|
assert ActiveRecord::SQLCounter.log_all.none? { |sql| sql.include?("followers_count") }
|
2012-09-28 12:55:35 -04:00
|
|
|
|
|
|
|
jon.reload
|
2016-08-06 12:26:20 -04:00
|
|
|
assert_equal "Jon", jon.first_name
|
2012-09-28 12:55:35 -04:00
|
|
|
assert_equal 0, jon.followers_count
|
|
|
|
assert_not_nil jon.id
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
test "partial insert with empty values" do
|
2012-10-19 11:45:41 -04:00
|
|
|
with_partial_writes Aircraft do
|
2012-09-28 12:55:35 -04:00
|
|
|
a = Aircraft.create!
|
|
|
|
a.reload
|
|
|
|
assert_not_nil a.id
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-07-12 20:30:49 -04:00
|
|
|
test "in place mutation detection" do
|
|
|
|
pirate = Pirate.create!(catchphrase: "arrrr")
|
|
|
|
pirate.catchphrase << " matey!"
|
|
|
|
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :catchphrase_changed?
|
2014-07-12 20:30:49 -04:00
|
|
|
expected_changes = {
|
|
|
|
"catchphrase" => ["arrrr", "arrrr matey!"]
|
|
|
|
}
|
|
|
|
assert_equal(expected_changes, pirate.changes)
|
|
|
|
assert_equal("arrrr", pirate.catchphrase_was)
|
|
|
|
assert pirate.catchphrase_changed?(from: "arrrr")
|
|
|
|
assert_not pirate.catchphrase_changed?(from: "anything else")
|
2016-09-16 12:44:05 -04:00
|
|
|
assert_includes pirate.changed_attributes, :catchphrase
|
2014-07-12 20:30:49 -04:00
|
|
|
|
|
|
|
pirate.save!
|
|
|
|
pirate.reload
|
|
|
|
|
|
|
|
assert_equal "arrrr matey!", pirate.catchphrase
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate pirate, :changed?
|
2014-07-12 20:30:49 -04:00
|
|
|
end
|
|
|
|
|
2014-08-26 10:43:04 -04:00
|
|
|
test "in place mutation for binary" do
|
|
|
|
klass = Class.new(ActiveRecord::Base) do
|
|
|
|
self.table_name = :binaries
|
|
|
|
serialize :data
|
|
|
|
end
|
|
|
|
|
2014-11-19 16:16:22 -05:00
|
|
|
binary = klass.create!(data: "\\\\foo")
|
|
|
|
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate binary, :changed?
|
2014-11-19 16:16:22 -05:00
|
|
|
|
|
|
|
binary.data = binary.data.dup
|
|
|
|
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate binary, :changed?
|
2014-11-19 16:16:22 -05:00
|
|
|
|
2014-08-28 06:27:22 -04:00
|
|
|
binary = klass.last
|
2014-08-26 10:43:04 -04:00
|
|
|
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate binary, :changed?
|
2014-08-26 10:43:04 -04:00
|
|
|
|
|
|
|
binary.data << "bar"
|
|
|
|
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate binary, :changed?
|
2014-08-26 10:43:04 -04:00
|
|
|
end
|
|
|
|
|
2017-04-14 13:46:07 -04:00
|
|
|
test "changes is correct for subclass" do
|
|
|
|
foo = Class.new(Pirate) do
|
|
|
|
def catchphrase
|
|
|
|
super.upcase
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
pirate = foo.create!(catchphrase: "arrrr")
|
|
|
|
|
|
|
|
new_catchphrase = "arrrr matey!"
|
|
|
|
|
|
|
|
pirate.catchphrase = new_catchphrase
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :catchphrase_changed?
|
2017-04-14 13:46:07 -04:00
|
|
|
|
|
|
|
expected_changes = {
|
|
|
|
"catchphrase" => ["arrrr", new_catchphrase]
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_equal new_catchphrase.upcase, pirate.catchphrase
|
|
|
|
assert_equal expected_changes, pirate.changes
|
|
|
|
end
|
|
|
|
|
2017-04-04 13:23:51 -04:00
|
|
|
test "changes is correct if override attribute reader" do
|
|
|
|
pirate = Pirate.create!(catchphrase: "arrrr")
|
|
|
|
def pirate.catchphrase
|
|
|
|
super.upcase
|
|
|
|
end
|
|
|
|
|
|
|
|
new_catchphrase = "arrrr matey!"
|
|
|
|
|
|
|
|
pirate.catchphrase = new_catchphrase
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :catchphrase_changed?
|
2017-04-04 13:23:51 -04:00
|
|
|
|
|
|
|
expected_changes = {
|
|
|
|
"catchphrase" => ["arrrr", new_catchphrase]
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_equal new_catchphrase.upcase, pirate.catchphrase
|
|
|
|
assert_equal expected_changes, pirate.changes
|
|
|
|
end
|
|
|
|
|
2014-12-22 16:55:58 -05:00
|
|
|
test "attribute_changed? doesn't compute in-place changes for unrelated attributes" do
|
|
|
|
test_type_class = Class.new(ActiveRecord::Type::Value) do
|
|
|
|
define_method(:changed_in_place?) do |*|
|
|
|
|
raise
|
|
|
|
end
|
|
|
|
end
|
|
|
|
klass = Class.new(ActiveRecord::Base) do
|
2016-08-06 12:26:20 -04:00
|
|
|
self.table_name = "people"
|
2014-12-22 16:55:58 -05:00
|
|
|
attribute :foo, test_type_class.new
|
|
|
|
end
|
|
|
|
|
|
|
|
model = klass.new(first_name: "Jim")
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate model, :first_name_changed?
|
2014-12-22 16:55:58 -05:00
|
|
|
end
|
|
|
|
|
2015-01-10 13:57:14 -05:00
|
|
|
test "attribute_will_change! doesn't try to save non-persistable attributes" do
|
|
|
|
klass = Class.new(ActiveRecord::Base) do
|
2016-08-06 12:26:20 -04:00
|
|
|
self.table_name = "people"
|
2015-02-06 13:05:38 -05:00
|
|
|
attribute :non_persisted_attribute, :string
|
2015-01-10 13:57:14 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
record = klass.new(first_name: "Sean")
|
|
|
|
record.non_persisted_attribute_will_change!
|
|
|
|
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate record, :non_persisted_attribute_changed?
|
2015-01-10 13:57:14 -05:00
|
|
|
assert record.save
|
|
|
|
end
|
|
|
|
|
2018-02-26 13:16:10 -05:00
|
|
|
test "virtual attributes are not written with partial_writes off" do
|
2018-02-27 17:29:44 -05:00
|
|
|
with_partial_writes(ActiveRecord::Base, false) do
|
2018-02-26 13:16:10 -05:00
|
|
|
klass = Class.new(ActiveRecord::Base) do
|
|
|
|
self.table_name = "people"
|
|
|
|
attribute :non_persisted_attribute, :string
|
|
|
|
end
|
|
|
|
|
|
|
|
record = klass.new(first_name: "Sean")
|
|
|
|
record.non_persisted_attribute_will_change!
|
|
|
|
|
2018-02-26 13:25:45 -05:00
|
|
|
assert record.save
|
|
|
|
|
|
|
|
record.non_persisted_attribute_will_change!
|
|
|
|
|
2018-02-26 13:16:10 -05:00
|
|
|
assert record.save
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-01-18 15:43:31 -05:00
|
|
|
test "mutating and then assigning doesn't remove the change" do
|
|
|
|
pirate = Pirate.create!(catchphrase: "arrrr")
|
|
|
|
pirate.catchphrase << " matey!"
|
|
|
|
pirate.catchphrase = "arrrr matey!"
|
|
|
|
|
|
|
|
assert pirate.catchphrase_changed?(from: "arrrr", to: "arrrr matey!")
|
|
|
|
end
|
|
|
|
|
2015-06-12 13:00:27 -04:00
|
|
|
test "getters with side effects are allowed" do
|
|
|
|
klass = Class.new(Pirate) do
|
|
|
|
def catchphrase
|
|
|
|
if super.blank?
|
|
|
|
update_attribute(:catchphrase, "arr") # what could possibly go wrong?
|
|
|
|
end
|
|
|
|
super
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
pirate = klass.create!(catchphrase: "lol")
|
|
|
|
pirate.update_attribute(:catchphrase, nil)
|
|
|
|
|
|
|
|
assert_equal "arr", pirate.catchphrase
|
|
|
|
end
|
|
|
|
|
2016-06-02 11:32:05 -04:00
|
|
|
test "attributes assigned but not selected are dirty" do
|
|
|
|
person = Person.select(:id).first
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate person, :changed?
|
2016-06-02 11:32:05 -04:00
|
|
|
|
|
|
|
person.first_name = "Sean"
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate person, :changed?
|
2016-06-02 11:32:05 -04:00
|
|
|
|
|
|
|
person.first_name = nil
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate person, :changed?
|
2016-06-02 11:32:05 -04:00
|
|
|
end
|
|
|
|
|
2017-05-09 06:04:22 -04:00
|
|
|
test "attributes not selected are still missing after save" do
|
|
|
|
person = Person.select(:id).first
|
|
|
|
assert_raises(ActiveModel::MissingAttributeError) { person.first_name }
|
|
|
|
assert person.save # calls forget_attribute_assignments
|
|
|
|
assert_raises(ActiveModel::MissingAttributeError) { person.first_name }
|
|
|
|
end
|
|
|
|
|
Deprecate the behavior of AR::Dirty inside of after_(create|update|save) callbacks
We pretty frequently get bug reports that "dirty is broken inside of
after callbacks". Intuitively they are correct. You'd expect
`Model.after_save { puts changed? }; model.save` to do the same thing as
`model.save; puts model.changed?`, but it does not.
However, changing this goes much farther than just making the behavior
more intuitive. There are a _ton_ of places inside of AR that can be
drastically simplified with this change. Specifically, autosave
associations, timestamps, touch, counter cache, and just about anything
else in AR that works with callbacks have code to try to avoid "double
save" bugs which we will be able to flat out remove with this change.
We introduce two new sets of methods, both with names that are meant to
be more explicit than dirty. The first set maintains the old behavior,
and their names are meant to center that they are about changes that
occurred during the save that just happened. They are equivalent to
`previous_changes` when called outside of after callbacks, or once the
deprecation cycle moves.
The second set is the new behavior. Their names imply that they are
talking about changes from the database representation. The fact that
this is what we really care about became clear when looking at
`BelongsTo.touch_record` when tests were failing. I'm unsure that this
set of methods should be in the public API. Outside of after callbacks,
they are equivalent to the existing methods on dirty.
Dirty itself is not deprecated, nor are the methods inside of it. They
will only emit the warning when called inside of after callbacks. The
scope of this breakage is pretty large, but the migration path is
simple. Given how much this can improve our codebase, and considering
that it makes our API more intuitive, I think it's worth doing.
2016-06-09 10:07:12 -04:00
|
|
|
test "saved_change_to_attribute? returns whether a change occurred in the last save" do
|
|
|
|
person = Person.create!(first_name: "Sean")
|
|
|
|
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate person, :saved_change_to_first_name?
|
|
|
|
assert_not_predicate person, :saved_change_to_gender?
|
Deprecate the behavior of AR::Dirty inside of after_(create|update|save) callbacks
We pretty frequently get bug reports that "dirty is broken inside of
after callbacks". Intuitively they are correct. You'd expect
`Model.after_save { puts changed? }; model.save` to do the same thing as
`model.save; puts model.changed?`, but it does not.
However, changing this goes much farther than just making the behavior
more intuitive. There are a _ton_ of places inside of AR that can be
drastically simplified with this change. Specifically, autosave
associations, timestamps, touch, counter cache, and just about anything
else in AR that works with callbacks have code to try to avoid "double
save" bugs which we will be able to flat out remove with this change.
We introduce two new sets of methods, both with names that are meant to
be more explicit than dirty. The first set maintains the old behavior,
and their names are meant to center that they are about changes that
occurred during the save that just happened. They are equivalent to
`previous_changes` when called outside of after callbacks, or once the
deprecation cycle moves.
The second set is the new behavior. Their names imply that they are
talking about changes from the database representation. The fact that
this is what we really care about became clear when looking at
`BelongsTo.touch_record` when tests were failing. I'm unsure that this
set of methods should be in the public API. Outside of after callbacks,
they are equivalent to the existing methods on dirty.
Dirty itself is not deprecated, nor are the methods inside of it. They
will only emit the warning when called inside of after callbacks. The
scope of this breakage is pretty large, but the migration path is
simple. Given how much this can improve our codebase, and considering
that it makes our API more intuitive, I think it's worth doing.
2016-06-09 10:07:12 -04:00
|
|
|
assert person.saved_change_to_first_name?(from: nil, to: "Sean")
|
|
|
|
assert person.saved_change_to_first_name?(from: nil)
|
|
|
|
assert person.saved_change_to_first_name?(to: "Sean")
|
2018-01-24 22:04:11 -05:00
|
|
|
assert_not person.saved_change_to_first_name?(from: "Jim", to: "Sean")
|
|
|
|
assert_not person.saved_change_to_first_name?(from: "Jim")
|
|
|
|
assert_not person.saved_change_to_first_name?(to: "Jim")
|
Deprecate the behavior of AR::Dirty inside of after_(create|update|save) callbacks
We pretty frequently get bug reports that "dirty is broken inside of
after callbacks". Intuitively they are correct. You'd expect
`Model.after_save { puts changed? }; model.save` to do the same thing as
`model.save; puts model.changed?`, but it does not.
However, changing this goes much farther than just making the behavior
more intuitive. There are a _ton_ of places inside of AR that can be
drastically simplified with this change. Specifically, autosave
associations, timestamps, touch, counter cache, and just about anything
else in AR that works with callbacks have code to try to avoid "double
save" bugs which we will be able to flat out remove with this change.
We introduce two new sets of methods, both with names that are meant to
be more explicit than dirty. The first set maintains the old behavior,
and their names are meant to center that they are about changes that
occurred during the save that just happened. They are equivalent to
`previous_changes` when called outside of after callbacks, or once the
deprecation cycle moves.
The second set is the new behavior. Their names imply that they are
talking about changes from the database representation. The fact that
this is what we really care about became clear when looking at
`BelongsTo.touch_record` when tests were failing. I'm unsure that this
set of methods should be in the public API. Outside of after callbacks,
they are equivalent to the existing methods on dirty.
Dirty itself is not deprecated, nor are the methods inside of it. They
will only emit the warning when called inside of after callbacks. The
scope of this breakage is pretty large, but the migration path is
simple. Given how much this can improve our codebase, and considering
that it makes our API more intuitive, I think it's worth doing.
2016-06-09 10:07:12 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
test "saved_change_to_attribute returns the change that occurred in the last save" do
|
|
|
|
person = Person.create!(first_name: "Sean", gender: "M")
|
|
|
|
|
|
|
|
assert_equal [nil, "Sean"], person.saved_change_to_first_name
|
|
|
|
assert_equal [nil, "M"], person.saved_change_to_gender
|
|
|
|
|
|
|
|
person.update(first_name: "Jim")
|
|
|
|
|
|
|
|
assert_equal ["Sean", "Jim"], person.saved_change_to_first_name
|
|
|
|
assert_nil person.saved_change_to_gender
|
|
|
|
end
|
|
|
|
|
|
|
|
test "attribute_before_last_save returns the original value before saving" do
|
|
|
|
person = Person.create!(first_name: "Sean", gender: "M")
|
|
|
|
|
|
|
|
assert_nil person.first_name_before_last_save
|
|
|
|
assert_nil person.gender_before_last_save
|
|
|
|
|
|
|
|
person.first_name = "Jim"
|
|
|
|
|
|
|
|
assert_nil person.first_name_before_last_save
|
|
|
|
assert_nil person.gender_before_last_save
|
|
|
|
|
|
|
|
person.save
|
|
|
|
|
|
|
|
assert_equal "Sean", person.first_name_before_last_save
|
|
|
|
assert_equal "M", person.gender_before_last_save
|
|
|
|
end
|
|
|
|
|
|
|
|
test "saved_changes? returns whether the last call to save changed anything" do
|
|
|
|
person = Person.create!(first_name: "Sean")
|
|
|
|
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate person, :saved_changes?
|
Deprecate the behavior of AR::Dirty inside of after_(create|update|save) callbacks
We pretty frequently get bug reports that "dirty is broken inside of
after callbacks". Intuitively they are correct. You'd expect
`Model.after_save { puts changed? }; model.save` to do the same thing as
`model.save; puts model.changed?`, but it does not.
However, changing this goes much farther than just making the behavior
more intuitive. There are a _ton_ of places inside of AR that can be
drastically simplified with this change. Specifically, autosave
associations, timestamps, touch, counter cache, and just about anything
else in AR that works with callbacks have code to try to avoid "double
save" bugs which we will be able to flat out remove with this change.
We introduce two new sets of methods, both with names that are meant to
be more explicit than dirty. The first set maintains the old behavior,
and their names are meant to center that they are about changes that
occurred during the save that just happened. They are equivalent to
`previous_changes` when called outside of after callbacks, or once the
deprecation cycle moves.
The second set is the new behavior. Their names imply that they are
talking about changes from the database representation. The fact that
this is what we really care about became clear when looking at
`BelongsTo.touch_record` when tests were failing. I'm unsure that this
set of methods should be in the public API. Outside of after callbacks,
they are equivalent to the existing methods on dirty.
Dirty itself is not deprecated, nor are the methods inside of it. They
will only emit the warning when called inside of after callbacks. The
scope of this breakage is pretty large, but the migration path is
simple. Given how much this can improve our codebase, and considering
that it makes our API more intuitive, I think it's worth doing.
2016-06-09 10:07:12 -04:00
|
|
|
|
|
|
|
person.save
|
|
|
|
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate person, :saved_changes?
|
Deprecate the behavior of AR::Dirty inside of after_(create|update|save) callbacks
We pretty frequently get bug reports that "dirty is broken inside of
after callbacks". Intuitively they are correct. You'd expect
`Model.after_save { puts changed? }; model.save` to do the same thing as
`model.save; puts model.changed?`, but it does not.
However, changing this goes much farther than just making the behavior
more intuitive. There are a _ton_ of places inside of AR that can be
drastically simplified with this change. Specifically, autosave
associations, timestamps, touch, counter cache, and just about anything
else in AR that works with callbacks have code to try to avoid "double
save" bugs which we will be able to flat out remove with this change.
We introduce two new sets of methods, both with names that are meant to
be more explicit than dirty. The first set maintains the old behavior,
and their names are meant to center that they are about changes that
occurred during the save that just happened. They are equivalent to
`previous_changes` when called outside of after callbacks, or once the
deprecation cycle moves.
The second set is the new behavior. Their names imply that they are
talking about changes from the database representation. The fact that
this is what we really care about became clear when looking at
`BelongsTo.touch_record` when tests were failing. I'm unsure that this
set of methods should be in the public API. Outside of after callbacks,
they are equivalent to the existing methods on dirty.
Dirty itself is not deprecated, nor are the methods inside of it. They
will only emit the warning when called inside of after callbacks. The
scope of this breakage is pretty large, but the migration path is
simple. Given how much this can improve our codebase, and considering
that it makes our API more intuitive, I think it's worth doing.
2016-06-09 10:07:12 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
test "saved_changes returns a hash of all the changes that occurred" do
|
|
|
|
person = Person.create!(first_name: "Sean", gender: "M")
|
|
|
|
|
|
|
|
assert_equal [nil, "Sean"], person.saved_changes[:first_name]
|
|
|
|
assert_equal [nil, "M"], person.saved_changes[:gender]
|
|
|
|
assert_equal %w(id first_name gender created_at updated_at).sort, person.saved_changes.keys.sort
|
|
|
|
|
|
|
|
travel(1.second) do
|
|
|
|
person.update(first_name: "Jim")
|
|
|
|
end
|
|
|
|
|
|
|
|
assert_equal ["Sean", "Jim"], person.saved_changes[:first_name]
|
|
|
|
assert_equal %w(first_name lock_version updated_at).sort, person.saved_changes.keys.sort
|
|
|
|
end
|
|
|
|
|
2017-07-18 10:33:53 -04:00
|
|
|
test "changed? in after callbacks returns false" do
|
Deprecate the behavior of AR::Dirty inside of after_(create|update|save) callbacks
We pretty frequently get bug reports that "dirty is broken inside of
after callbacks". Intuitively they are correct. You'd expect
`Model.after_save { puts changed? }; model.save` to do the same thing as
`model.save; puts model.changed?`, but it does not.
However, changing this goes much farther than just making the behavior
more intuitive. There are a _ton_ of places inside of AR that can be
drastically simplified with this change. Specifically, autosave
associations, timestamps, touch, counter cache, and just about anything
else in AR that works with callbacks have code to try to avoid "double
save" bugs which we will be able to flat out remove with this change.
We introduce two new sets of methods, both with names that are meant to
be more explicit than dirty. The first set maintains the old behavior,
and their names are meant to center that they are about changes that
occurred during the save that just happened. They are equivalent to
`previous_changes` when called outside of after callbacks, or once the
deprecation cycle moves.
The second set is the new behavior. Their names imply that they are
talking about changes from the database representation. The fact that
this is what we really care about became clear when looking at
`BelongsTo.touch_record` when tests were failing. I'm unsure that this
set of methods should be in the public API. Outside of after callbacks,
they are equivalent to the existing methods on dirty.
Dirty itself is not deprecated, nor are the methods inside of it. They
will only emit the warning when called inside of after callbacks. The
scope of this breakage is pretty large, but the migration path is
simple. Given how much this can improve our codebase, and considering
that it makes our API more intuitive, I think it's worth doing.
2016-06-09 10:07:12 -04:00
|
|
|
klass = Class.new(ActiveRecord::Base) do
|
|
|
|
self.table_name = "people"
|
|
|
|
|
|
|
|
after_save do
|
2017-07-18 10:33:53 -04:00
|
|
|
raise "changed? should be false" if changed?
|
Deprecate the behavior of AR::Dirty inside of after_(create|update|save) callbacks
We pretty frequently get bug reports that "dirty is broken inside of
after callbacks". Intuitively they are correct. You'd expect
`Model.after_save { puts changed? }; model.save` to do the same thing as
`model.save; puts model.changed?`, but it does not.
However, changing this goes much farther than just making the behavior
more intuitive. There are a _ton_ of places inside of AR that can be
drastically simplified with this change. Specifically, autosave
associations, timestamps, touch, counter cache, and just about anything
else in AR that works with callbacks have code to try to avoid "double
save" bugs which we will be able to flat out remove with this change.
We introduce two new sets of methods, both with names that are meant to
be more explicit than dirty. The first set maintains the old behavior,
and their names are meant to center that they are about changes that
occurred during the save that just happened. They are equivalent to
`previous_changes` when called outside of after callbacks, or once the
deprecation cycle moves.
The second set is the new behavior. Their names imply that they are
talking about changes from the database representation. The fact that
this is what we really care about became clear when looking at
`BelongsTo.touch_record` when tests were failing. I'm unsure that this
set of methods should be in the public API. Outside of after callbacks,
they are equivalent to the existing methods on dirty.
Dirty itself is not deprecated, nor are the methods inside of it. They
will only emit the warning when called inside of after callbacks. The
scope of this breakage is pretty large, but the migration path is
simple. Given how much this can improve our codebase, and considering
that it makes our API more intuitive, I think it's worth doing.
2016-06-09 10:07:12 -04:00
|
|
|
raise "has_changes_to_save? should be false" if has_changes_to_save?
|
2017-07-18 11:08:00 -04:00
|
|
|
raise "saved_changes? should be true" unless saved_changes?
|
2018-08-22 07:00:19 -04:00
|
|
|
raise "id_in_database should not be nil" if id_in_database.nil?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
person = klass.create!(first_name: "Sean")
|
|
|
|
assert_not_predicate person, :changed?
|
|
|
|
end
|
|
|
|
|
|
|
|
test "changed? in around callbacks after yield returns false" do
|
|
|
|
klass = Class.new(ActiveRecord::Base) do
|
|
|
|
self.table_name = "people"
|
|
|
|
|
|
|
|
around_create :check_around
|
|
|
|
|
|
|
|
def check_around
|
|
|
|
yield
|
|
|
|
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?
|
|
|
|
raise "id_in_database should not be nil" if id_in_database.nil?
|
Deprecate the behavior of AR::Dirty inside of after_(create|update|save) callbacks
We pretty frequently get bug reports that "dirty is broken inside of
after callbacks". Intuitively they are correct. You'd expect
`Model.after_save { puts changed? }; model.save` to do the same thing as
`model.save; puts model.changed?`, but it does not.
However, changing this goes much farther than just making the behavior
more intuitive. There are a _ton_ of places inside of AR that can be
drastically simplified with this change. Specifically, autosave
associations, timestamps, touch, counter cache, and just about anything
else in AR that works with callbacks have code to try to avoid "double
save" bugs which we will be able to flat out remove with this change.
We introduce two new sets of methods, both with names that are meant to
be more explicit than dirty. The first set maintains the old behavior,
and their names are meant to center that they are about changes that
occurred during the save that just happened. They are equivalent to
`previous_changes` when called outside of after callbacks, or once the
deprecation cycle moves.
The second set is the new behavior. Their names imply that they are
talking about changes from the database representation. The fact that
this is what we really care about became clear when looking at
`BelongsTo.touch_record` when tests were failing. I'm unsure that this
set of methods should be in the public API. Outside of after callbacks,
they are equivalent to the existing methods on dirty.
Dirty itself is not deprecated, nor are the methods inside of it. They
will only emit the warning when called inside of after callbacks. The
scope of this breakage is pretty large, but the migration path is
simple. Given how much this can improve our codebase, and considering
that it makes our API more intuitive, I think it's worth doing.
2016-06-09 10:07:12 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
person = klass.create!(first_name: "Sean")
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_not_predicate person, :changed?
|
Deprecate the behavior of AR::Dirty inside of after_(create|update|save) callbacks
We pretty frequently get bug reports that "dirty is broken inside of
after callbacks". Intuitively they are correct. You'd expect
`Model.after_save { puts changed? }; model.save` to do the same thing as
`model.save; puts model.changed?`, but it does not.
However, changing this goes much farther than just making the behavior
more intuitive. There are a _ton_ of places inside of AR that can be
drastically simplified with this change. Specifically, autosave
associations, timestamps, touch, counter cache, and just about anything
else in AR that works with callbacks have code to try to avoid "double
save" bugs which we will be able to flat out remove with this change.
We introduce two new sets of methods, both with names that are meant to
be more explicit than dirty. The first set maintains the old behavior,
and their names are meant to center that they are about changes that
occurred during the save that just happened. They are equivalent to
`previous_changes` when called outside of after callbacks, or once the
deprecation cycle moves.
The second set is the new behavior. Their names imply that they are
talking about changes from the database representation. The fact that
this is what we really care about became clear when looking at
`BelongsTo.touch_record` when tests were failing. I'm unsure that this
set of methods should be in the public API. Outside of after callbacks,
they are equivalent to the existing methods on dirty.
Dirty itself is not deprecated, nor are the methods inside of it. They
will only emit the warning when called inside of after callbacks. The
scope of this breakage is pretty large, but the migration path is
simple. Given how much this can improve our codebase, and considering
that it makes our API more intuitive, I think it's worth doing.
2016-06-09 10:07:12 -04:00
|
|
|
end
|
|
|
|
|
2008-03-30 21:10:04 -04:00
|
|
|
private
|
2012-10-19 11:45:41 -04:00
|
|
|
def with_partial_writes(klass, on = true)
|
|
|
|
old = klass.partial_writes?
|
|
|
|
klass.partial_writes = on
|
2008-03-30 21:10:04 -04:00
|
|
|
yield
|
|
|
|
ensure
|
2012-10-19 11:45:41 -04:00
|
|
|
klass.partial_writes = old
|
2008-03-30 21:10:04 -04:00
|
|
|
end
|
2008-05-12 18:46:57 -04:00
|
|
|
|
|
|
|
def check_pirate_after_save_failure(pirate)
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate pirate, :changed?
|
|
|
|
assert_predicate pirate, :parrot_id_changed?
|
2008-05-12 18:46:57 -04:00
|
|
|
assert_equal %w(parrot_id), pirate.changed
|
|
|
|
assert_nil pirate.parrot_id_was
|
|
|
|
end
|
2008-03-28 20:04:27 -04:00
|
|
|
end
|