aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activemodel/lib/active_model/type.rb2
-rw-r--r--activemodel/lib/active_model/type/immutable_string.rb29
-rw-r--r--activemodel/lib/active_model/type/string.rb25
-rw-r--r--activemodel/test/cases/type/string_test.rb7
-rw-r--r--activerecord/CHANGELOG.md12
-rw-r--r--activerecord/lib/active_record/relation.rb7
-rw-r--r--activerecord/test/cases/relations_test.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/object/deep_dup.rb8
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb2
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb15
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