ActiveRecord store deserialization fails with nested objects
Steps to reproduce
# frozen_string_literal: true
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem "rails", github: "rails/rails", branch: "main"
gem "sqlite3"
end
require "active_record"
require "minitest/autorun"
require "logger"
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
create_table :posts, force: true do |t|
t.text "properties"
end
end
class Post < ActiveRecord::Base
store :properties
end
class BugTest < Minitest::Test
def test_store_yaml_serialization
post = Post.create! properties: { scalar_value: 2, nested_properties: { a: 3, b: 4 } }
assert_equal 2, post.properties[:scalar_value]
end
end
Expected behavior
The serialization/deserialization round trip should work.
Actual behavior
A Psych::DisallowedClass
error is raised:
BugTest#test_store_yaml_serialization:
Psych::DisallowedClass: Tried to load unspecified class: ActiveSupport::HashWithIndifferentAccess
/usr/lib/ruby/2.7.0/psych/class_loader.rb:97:in `find'
/usr/lib/ruby/2.7.0/psych/class_loader.rb:28:in `load'
/usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:411:in `resolve_class'
/usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:283:in `visit_Psych_Nodes_Mapping'
/usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:16:in `visit'
/usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:6:in `accept'
/usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:32:in `accept'
/usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:340:in `block in revive_hash'
/usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:338:in `each'
/usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:338:in `each_slice'
/usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:338:in `revive_hash'
/usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:162:in `visit_Psych_Nodes_Mapping'
/usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:16:in `visit'
/usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:6:in `accept'
/usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:32:in `accept'
/usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:313:in `visit_Psych_Nodes_Document'
/usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:16:in `visit'
/usr/lib/ruby/2.7.0/psych/visitors/visitor.rb:6:in `accept'
/usr/lib/ruby/2.7.0/psych/visitors/to_ruby.rb:32:in `accept'
/usr/lib/ruby/2.7.0/psych.rb:360:in `safe_load'
.../.bundle/ruby/2.7.0/rails-241893900740/activerecord/lib/active_record/coders/yaml_column.rb:61:in `yaml_load'
.../.bundle/ruby/2.7.0/rails-241893900740/activerecord/lib/active_record/coders/yaml_column.rb:26:in `load'
.../.bundle/ruby/2.7.0/rails-241893900740/activerecord/lib/active_record/store.rb:275:in `load'
.../.bundle/ruby/2.7.0/rails-241893900740/activerecord/lib/active_record/type/serialized.rb:22:in `deserialize'
.../.bundle/ruby/2.7.0/rails-241893900740/activemodel/lib/active_model/attribute.rb:168:in `type_cast'
.../.bundle/ruby/2.7.0/rails-241893900740/activemodel/lib/active_model/attribute.rb:43:in `value'
.../.bundle/ruby/2.7.0/rails-241893900740/activemodel/lib/active_model/attribute_set.rb:51:in `fetch_value'
.../.bundle/ruby/2.7.0/rails-241893900740/activerecord/lib/active_record/attribute_methods/read.rb:38:in `_read_attribute'
.../.bundle/ruby/2.7.0/rails-241893900740/activemodel/lib/active_model/attribute_methods.rb:277:in `properties'
serialize-case.rb:35:in `test_store_yaml_serialization'
The issue #45585 (closed) was marked as fixed with PR #45591, but that fix is incomplete. It does not take into account nested objects: e.g. in my example above, the top-level properties
is converted from HashWithIndifferentAccess
to a regular hash, but even after that conversion, properties[:nested_properties]
remains a HashWithIndifferentAccess
. Activerecord::Store::IndifferentCoder#as_regular_hash
should work in a recursive fashion.
System configuration
Rails version: 24189390 (7.1.0.alpha)
Ruby version: 2.7.4p191