Skip to content

Commit f24de2c

Browse files
committed
Fix deserialization with legacy --- HashWithIndifferentAccess
1 parent 7f1fb55 commit f24de2c

File tree

2 files changed

+84
-1
lines changed

2 files changed

+84
-1
lines changed

config/application.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ class Application < Rails::Application
111111
ActionController::Parameters,
112112
ActiveSupport::TimeWithZone,
113113
ActiveSupport::TimeZone,
114-
ActiveSupport::HashWithIndifferentAccess]
114+
ActiveSupport::HashWithIndifferentAccess,
115+
'HashWithIndifferentAccess']
115116

116117
# Keeping the historic behavior by setting to `YAML`
117118
# It is recommended to explicitly define the serialization method for each column
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# frozen_string_literal: true
2+
3+
require 'test_helper'
4+
5+
class YamlSerializationLegacyFormatTest < ActiveSupport::TestCase
6+
# This test ensures that legacy YAML data serialized with !map:HashWithIndifferentAccess
7+
# can be deserialized properly. This format was used in older versions of Rails.
8+
#
9+
# CONTEXT: The primary use case is the `extra_fields` column on the `cinstances` table.
10+
# Extra fields are custom fields defined by FieldsDefinition and stored as a Hash
11+
# in the extra_fields column (serialized as YAML). The keys are the field names
12+
# and values are user-provided data.
13+
#
14+
# The fix is in config/application.rb where 'HashWithIndifferentAccess' (as a string)
15+
# is added to yaml_column_permitted_classes.
16+
#
17+
# Without this fix, loading old YAML would raise:
18+
# Psych::DisallowedClass: Tried to load unspecified class: HashWithIndifferentAccess
19+
20+
test 'can deserialize legacy HashWithIndifferentAccess YAML format in Cinstance extra_fields' do
21+
# Setup: Create a provider account that will own the fields definitions
22+
provider = FactoryBot.create(:provider_account)
23+
24+
# Setup: Create custom field definition for Cinstance (applications)
25+
FactoryBot.create(:fields_definition, account: provider,
26+
target: 'Cinstance', name: 'custom_field', label: 'Custom Field'
27+
)
28+
29+
# Setup: Create a buyer account and an application (Cinstance)
30+
plan = FactoryBot.create(:application_plan, issuer: provider.first_service!)
31+
application = FactoryBot.create(:cinstance, plan: plan)
32+
33+
# Simulate legacy data: Update the extra_fields column directly with old YAML format
34+
# This is what exists in production databases from older Rails versions
35+
legacy_yaml = <<~YAML.strip
36+
--- !map:HashWithIndifferentAccess
37+
custom_field: custom_value
38+
YAML
39+
40+
ActiveRecord::Base.connection.execute(
41+
"UPDATE cinstances SET extra_fields = #{ActiveRecord::Base.connection.quote(legacy_yaml)} WHERE id = #{application.id}"
42+
)
43+
44+
# Test: Reload the application and verify the legacy YAML can be deserialized
45+
application.reload
46+
47+
assert_instance_of ActiveSupport::HashWithIndifferentAccess, application.extra_fields
48+
49+
# Verify the field values are correctly deserialized
50+
assert_equal 'custom_value', application.extra_fields['custom_field']
51+
assert_equal 'custom_value', application.extra_fields[:custom_field]
52+
53+
# Re-save to trigger modern serialization
54+
application.save!
55+
56+
# Check the raw database value - should not contain old tag
57+
raw_yaml = ActiveRecord::Base.connection.select_value(
58+
"SELECT extra_fields FROM cinstances WHERE id = #{application.id}"
59+
)
60+
61+
refute_includes raw_yaml, '!map:HashWithIndifferentAccess',
62+
'Re-serialized extra_fields should use modern format'
63+
64+
# But data should still be accessible
65+
application.reload
66+
assert_equal 'custom_value', application.extra_fields['custom_field']
67+
assert_equal 'custom_value', application.extra_fields[:custom_field]
68+
end
69+
70+
test 'legacy YAML format is included in permitted classes configuration' do
71+
permitted_classes = Rails.application.config.active_record.yaml_column_permitted_classes
72+
73+
# Must include the string 'HashWithIndifferentAccess' for Psych to resolve the YAML tag
74+
# This is critical for deserializing old extra_fields data in cinstances and other models
75+
assert_includes permitted_classes, 'HashWithIndifferentAccess',
76+
"Config must include 'HashWithIndifferentAccess' string to support legacy YAML format in extra_fields"
77+
78+
# Also verify the class itself is included (for modern YAML)
79+
assert_includes permitted_classes, ActiveSupport::HashWithIndifferentAccess,
80+
"Config should include ActiveSupport::HashWithIndifferentAccess class"
81+
end
82+
end

0 commit comments

Comments
 (0)