diff options
-rw-r--r-- | activemodel/lib/active_model/type.rb | 2 | ||||
-rw-r--r-- | activemodel/lib/active_model/type/immutable_string.rb | 29 | ||||
-rw-r--r-- | activemodel/lib/active_model/type/string.rb | 25 | ||||
-rw-r--r-- | activemodel/test/cases/type/string_test.rb | 7 | ||||
-rw-r--r-- | activerecord/CHANGELOG.md | 12 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation.rb | 7 | ||||
-rw-r--r-- | activerecord/test/cases/relations_test.rb | 7 | ||||
-rw-r--r-- | activesupport/lib/active_support/core_ext/object/deep_dup.rb | 8 | ||||
-rw-r--r-- | activesupport/lib/active_support/time_with_zone.rb | 2 | ||||
-rw-r--r-- | activesupport/test/core_ext/time_with_zone_test.rb | 15 |
10 files changed, 90 insertions, 24 deletions
diff --git a/activemodel/lib/active_model/type.rb b/activemodel/lib/active_model/type.rb index f8ca7d0512..bec851594f 100644 --- a/activemodel/lib/active_model/type.rb +++ b/activemodel/lib/active_model/type.rb @@ -9,6 +9,7 @@ require 'active_model/type/date_time' require 'active_model/type/decimal' require 'active_model/type/decimal_without_scale' require 'active_model/type/float' +require 'active_model/type/immutable_string' require 'active_model/type/integer' require 'active_model/type/string' require 'active_model/type/text' @@ -49,6 +50,7 @@ module ActiveModel register(:date_time, Type::DateTime) register(:decimal, Type::Decimal) register(:float, Type::Float) + register(:immutable_string, Type::ImmutableString) register(:integer, Type::Integer) register(:string, Type::String) register(:text, Type::Text) diff --git a/activemodel/lib/active_model/type/immutable_string.rb b/activemodel/lib/active_model/type/immutable_string.rb new file mode 100644 index 0000000000..20b8ca0cc4 --- /dev/null +++ b/activemodel/lib/active_model/type/immutable_string.rb @@ -0,0 +1,29 @@ +module ActiveModel + module Type + class ImmutableString < Value # :nodoc: + def type + :string + end + + def serialize(value) + case value + when ::Numeric, ActiveSupport::Duration then value.to_s + when true then "t" + when false then "f" + else super + end + end + + private + + def cast_value(value) + result = case value + when true then "t" + when false then "f" + else value.to_s + end + result.freeze + end + end + end +end diff --git a/activemodel/lib/active_model/type/string.rb b/activemodel/lib/active_model/type/string.rb index fd1630c751..8a91410998 100644 --- a/activemodel/lib/active_model/type/string.rb +++ b/activemodel/lib/active_model/type/string.rb @@ -1,35 +1,18 @@ +require "active_model/type/immutable_string" + module ActiveModel module Type - class String < Value # :nodoc: - def type - :string - end - + class String < ImmutableString # :nodoc: def changed_in_place?(raw_old_value, new_value) if new_value.is_a?(::String) raw_old_value != new_value end end - def serialize(value) - case value - when ::Numeric, ActiveSupport::Duration then value.to_s - when ::String then ::String.new(value) - when true then "t" - when false then "f" - else super - end - end - private def cast_value(value) - case value - when true then "t" - when false then "f" - # String.new is slightly faster than dup - else ::String.new(value.to_s) - end + ::String.new(super) end end end diff --git a/activemodel/test/cases/type/string_test.rb b/activemodel/test/cases/type/string_test.rb index 8ec771ea42..7b25a1ef74 100644 --- a/activemodel/test/cases/type/string_test.rb +++ b/activemodel/test/cases/type/string_test.rb @@ -10,6 +10,13 @@ module ActiveModel assert_equal "123", type.cast(123) end + test "immutable strings are not duped coming out" do + s = "foo" + type = Type::ImmutableString.new + assert_same s, type.cast(s) + assert_same s, type.deserialize(s) + end + test "values are duped coming out" do s = "foo" type = Type::String.new diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 026bb71949..41f6ab5342 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,15 @@ +* Add an immutable string type to help reduce memory usage for apps which do + not need mutation detection on Strings. + + *Sean Griffin* + +* Give `AcriveRecord::Relation#update` its own deprecation warning when + passed an `ActiveRecord::Base` instance. + + Fixes #21945. + + *Ted Johansson* + * Make it possible to pass `:to_table` when adding a foreign key through `add_reference`. diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 05bf87ab9d..392b462aa9 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -417,6 +417,13 @@ module ActiveRecord elsif id == :all to_a.each { |record| record.update(attributes) } else + if ActiveRecord::Base === id + id = id.id + ActiveSupport::Deprecation.warn(<<-MSG.squish) + You are passing an instance of ActiveRecord::Base to `update`. + Please pass the id of the object by calling `.id` + MSG + end object = find(id) object.update(attributes) object diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 8256762f96..7521f0573a 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1541,6 +1541,13 @@ class RelationTest < ActiveRecord::TestCase assert_equal 'David', topic2.reload.author_name end + def test_update_on_relation_passing_active_record_object_is_deprecated + topic = Topic.create!(title: 'Foo', author_name: nil) + assert_deprecated(/update/) do + Topic.where(id: topic.id).update(topic, title: 'Bar') + end + end + def test_distinct tag1 = Tag.create(:name => 'Foo') tag2 = Tag.create(:name => 'Foo') diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb index ad5b2af161..0e6962a378 100644 --- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb +++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb @@ -40,8 +40,12 @@ class Hash # dup[:a][:c] # => "c" def deep_dup each_with_object(dup) do |(key, value), hash| - hash.delete(key) - hash[key.deep_dup] = value.deep_dup + if key.frozen? && ::String === key + hash[key] = value.deep_dup + else + hash.delete(key) + hash[key.deep_dup] = value.deep_dup + end end end end diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 3592dcba39..910c1f91a5 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -102,7 +102,7 @@ module ActiveSupport # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' # Time.zone.now.utc? # => false def utc? - time_zone.name == 'UTC' + period.offset.abbreviation == :UTC || period.offset.abbreviation == :UCT end alias_method :gmt?, :utc? diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index c40f0bacbf..7acada011d 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -51,7 +51,22 @@ class TimeWithZoneTest < ActiveSupport::TestCase def test_utc? assert_equal false, @twz.utc? + assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']).utc? + assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Etc/UTC']).utc? + assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Universal']).utc? + assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UCT']).utc? + assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Etc/UCT']).utc? + assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Etc/Universal']).utc? + + assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Africa/Abidjan']).utc? + assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Africa/Banjul']).utc? + assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Africa/Freetown']).utc? + assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['GMT']).utc? + assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['GMT0']).utc? + assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Greenwich']).utc? + assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Iceland']).utc? + assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Africa/Monrovia']).utc? end def test_formatted_offset |