Skip to content

Commit 1b84587

Browse files
authored
Fix Type#deserialize to handle arrays for multiple: true attributes (#473)
Previously, deserializing an array would return nil instead of the enumerated values. This bug only affected ActiveModel::Attributes usage (not ActiveRecord) and was exposed when used with gems like store_model v2.0.0+ that call deserialize during load. The fix checks if the value is an array AND the attribute is declared with multiple: true, then uses find_values(*value) instead of find_value(value). Added 10 test cases covering: - Single value deserialization (existing behavior) - Array deserialization for multiple: true attributes - Array rejection for non-multiple attributes - Empty arrays, nil handling, and invalid value filtering - Round-trip serialization for both single and multiple values
1 parent 4084ab4 commit 1b84587

File tree

3 files changed

+87
-1
lines changed

3 files changed

+87
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
### bug fix
44

5+
* Fixed `Enumerize::ActiveModelAttributesSupport::Type#deserialize` to properly handle arrays for `multiple: true` attributes. Previously, deserializing an array would return `nil` instead of the enumerated values. This bug only affected ActiveModel::Attributes usage (not ActiveRecord) and was exposed when used with gems like store_model v2.0.0+ that call `deserialize` during load.
6+
57
### enchancements
68

79
* Support only Ruby 3.1+ and Rails 7.0+. (by [@nashby](https://github.com/nashby))

lib/enumerize/activemodel.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,15 @@ def serialize(value)
4040
end
4141

4242
def deserialize(value)
43-
@attr.find_value(value)
43+
return nil if value.nil?
44+
45+
# Use find_values for arrays on multiple: true attributes
46+
# Otherwise use find_value for single values
47+
if value.is_a?(Array) && @attr.arguments[:multiple]
48+
@attr.find_values(*value)
49+
else
50+
@attr.find_value(value)
51+
end
4452
end
4553
end
4654
end

test/activemodel_test.rb

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,82 @@ class InterestsRequiredActiveModelUser < ActiveModelUser
107107

108108
expect(user.errors[:interests]).must_be_empty
109109
end
110+
111+
describe 'Type#deserialize' do
112+
it 'deserializes single value' do
113+
type = model.attribute_types['sex']
114+
result = type.deserialize('male')
115+
expect(result).must_be_instance_of Enumerize::Value
116+
expect(result.to_s).must_equal 'male'
117+
end
118+
119+
it 'returns nil for nil single value' do
120+
type = model.attribute_types['sex']
121+
result = type.deserialize(nil)
122+
expect(result).must_be_nil
123+
end
124+
125+
it 'returns nil for invalid single value' do
126+
type = model.attribute_types['sex']
127+
result = type.deserialize('invalid')
128+
expect(result).must_be_nil
129+
end
130+
131+
it 'treats array as invalid for non-multiple attribute' do
132+
type = model.attribute_types['sex']
133+
result = type.deserialize(['male', 'female'])
134+
expect(result).must_be_nil
135+
end
136+
137+
it 'deserializes array of valid values for multiple attribute' do
138+
type = model.attribute_types['interests']
139+
result = type.deserialize(['music', 'sports'])
140+
expect(result).must_be_instance_of Array
141+
expect(result.map(&:to_s)).must_equal ['music', 'sports']
142+
end
143+
144+
it 'deserializes empty array for multiple attribute' do
145+
type = model.attribute_types['interests']
146+
result = type.deserialize([])
147+
expect(result).must_equal []
148+
end
149+
150+
it 'filters out invalid values from array' do
151+
type = model.attribute_types['interests']
152+
result = type.deserialize(['music', 'invalid', 'sports'])
153+
expect(result.map(&:to_s)).must_equal ['music', 'sports']
154+
end
155+
156+
it 'returns nil for nil array value' do
157+
type = model.attribute_types['interests']
158+
result = type.deserialize(nil)
159+
expect(result).must_be_nil
160+
end
161+
162+
it 'preserves values through serialize/deserialize cycle for single value' do
163+
type = model.attribute_types['sex']
164+
user = model.new(sex: 'female')
165+
166+
serialized = type.serialize(user.sex)
167+
expect(serialized).must_equal 'female'
168+
169+
deserialized = type.deserialize(serialized)
170+
expect(deserialized.to_s).must_equal 'female'
171+
end
172+
173+
it 'preserves values through serialize/deserialize cycle for multiple values' do
174+
type = model.attribute_types['interests']
175+
user = model.new(interests: ['music', 'programming'])
176+
177+
# Serialize the entire set
178+
serialized = user.interests.map { |v| type.serialize(v) }
179+
expect(serialized).must_equal ['music', 'programming']
180+
181+
# Deserialize back
182+
deserialized = type.deserialize(serialized)
183+
expect(deserialized.map(&:to_s)).must_equal ['music', 'programming']
184+
end
185+
end
110186
end
111187

112188
else

0 commit comments

Comments
 (0)