Skip to content

Commit

Permalink
Merge pull request #47352 from basecamp/ar-freeze-cached
Browse files Browse the repository at this point in the history
Fix querying of serialized Class attributes

Fix: #47352
  • Loading branch information
byroot committed May 2, 2023
2 parents 912096d + f195e03 commit 2c20f90
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 7 deletions.
8 changes: 6 additions & 2 deletions activemodel/lib/active_model/attribute.rb
Expand Up @@ -115,13 +115,17 @@ def has_been_read?
def ==(other)
self.class == other.class &&
name == other.name &&
value_before_type_cast == other.value_before_type_cast &&
identifier_value == other.identifier_value &&
type == other.type
end
alias eql? ==

def hash
[self.class, name, value_before_type_cast, type].hash
[self.class, name, identifier_value, type].hash
end

def identifier_value
value_before_type_cast
end

def init_with(coder)
Expand Down
4 changes: 0 additions & 4 deletions activemodel/lib/active_model/type/helpers/mutable.rb
Expand Up @@ -4,10 +4,6 @@ module ActiveModel
module Type
module Helpers # :nodoc: all
module Mutable
def immutable_value(value)
value.deep_dup.freeze
end

def cast(value)
deserialize(serialize(value))
end
Expand Down
4 changes: 4 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,3 +1,7 @@
* Fix error when querying serialized Class attributes.

*Jorge Manrubia*

* Add support for `Array#intersect?` to `ActiveRecord::Relation`.

`Array#intersect?` is only available on Ruby 3.1 or later.
Expand Down
Expand Up @@ -66,7 +66,7 @@ def build(attribute, value, operator = nil)

def build_bind_attribute(column_name, value)
type = table.type(column_name)
Relation::QueryAttribute.new(column_name, type.immutable_value(value), type)
Relation::QueryAttribute.new(column_name, value, type)
end

def resolve_arel_attribute(table_name, column_name, &block)
Expand Down
14 changes: 14 additions & 0 deletions activerecord/lib/active_record/relation/query_attribute.rb
Expand Up @@ -36,10 +36,24 @@ def unboundable?
@_unboundable
end

def identifier_value
if unboundable?
super
else
# Override to define equality based on frozen casted values to prevent caching
# issues with mutable database query values.
value_for_database
end
end

private
def infinity?(value)
value.respond_to?(:infinite?) && value.infinite?
end

def _value_for_database
super.deep_dup.freeze
end
end
end
end
18 changes: 18 additions & 0 deletions activerecord/test/cases/serialized_attribute_test.rb
Expand Up @@ -9,6 +9,11 @@
class SerializedAttributeTest < ActiveRecord::TestCase
def setup
ActiveRecord.use_yaml_unsafe_load = true
@yaml_column_permitted_classes_default = ActiveRecord.yaml_column_permitted_classes
end

def teardown
ActiveRecord.yaml_column_permitted_classes = @yaml_column_permitted_classes_default
end

fixtures :topics, :posts
Expand All @@ -23,6 +28,10 @@ class ImportantTopic < Topic
serialize :important, type: Hash
end

class ClassifiedTopic < Topic
serialize :important, type: Class
end

teardown do
Topic.serialize("content")
end
Expand Down Expand Up @@ -166,6 +175,14 @@ def test_serialized_string_attribute
assert_equal(myobj, topic.content)
end

def test_serialized_class_attribute
ActiveRecord.yaml_column_permitted_classes += [Class]

topic = ClassifiedTopic.create(important: Symbol).reload
assert_equal(Symbol, topic.important)
assert_not_empty ClassifiedTopic.where(important: Symbol)
end

def test_nil_serialized_attribute_without_class_constraint
topic = Topic.new
assert_nil topic.content
Expand Down Expand Up @@ -551,6 +568,7 @@ def test_serialized_attribute_works_under_concurrent_initial_access

class SerializedAttributeTestWithYamlSafeLoad < SerializedAttributeTest
def setup
super
ActiveRecord.use_yaml_unsafe_load = false
end

Expand Down

0 comments on commit 2c20f90

Please sign in to comment.