Skip to content

Commit aa69025

Browse files
authored
Fix Type#cast to support Rails 8.0 bulk operations (#469)
* Add comprehensive tests for ActiveRecord Type#cast and bulk operations - Test Type#cast returns Enumerize::Value for symbols, integers, and strings - Test Type#cast handles invalid values and preserves existing Value objects - Test insert_all/upsert_all work with mixed value types and handle invalid values * Fix Type#cast to handle symbol values correctly in Rails 8.0 bulk operations Rails 8.0 changed how Type#cast is used during insert_all/upsert_all operations, causing symbol enum values (e.g., :active) to fail. The previous implementation always delegated to @subtype.cast first, which could incorrectly transform symbol values before looking them up. This fix: - First attempts to find the enumerize value directly with the input value - Only delegates to @subtype.cast if the direct lookup fails - Ensures both symbol values (:active) and their database representations (1) work correctly This maintains backward compatibility while fixing bulk operations in Rails 8.0.
1 parent 22049b4 commit aa69025

File tree

2 files changed

+109
-5
lines changed

2 files changed

+109
-5
lines changed

lib/enumerize/activerecord.rb

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,14 @@ def serialize(value)
123123

124124
def cast(value)
125125
return value if @subtype.is_a?(Type)
126+
return value if value.is_a?(::Enumerize::Value)
126127

127-
if value.is_a?(::Enumerize::Value)
128-
value
129-
else
130-
@attr.find_value(@subtype.cast(value))
131-
end
128+
# First try to find the enumerize value directly
129+
enumerize_value = @attr.find_value(value)
130+
return enumerize_value if enumerize_value
131+
132+
# If not found, delegate to subtype then try to find value
133+
@attr.find_value(@subtype.cast(value))
132134
end
133135

134136
def as_json(options = nil)

test/activerecord_test.rb

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,4 +754,106 @@ class AdminUser < User
754754
expect(User.exists?(status: :active)).must_equal true
755755
expect(User.exists?(interests: [:music, :sports])).must_equal true
756756
end
757+
758+
it 'Type#cast returns Enumerize::Value for valid symbols' do
759+
type = User.type_for_attribute('status')
760+
result = type.cast(:active)
761+
762+
expect(result).must_be_instance_of Enumerize::Value
763+
expect(result.to_s).must_equal 'active'
764+
expect(result.value).must_equal 1
765+
end
766+
767+
it 'Type#cast returns Enumerize::Value for integers' do
768+
type = User.type_for_attribute('status')
769+
result = type.cast(1)
770+
771+
expect(result).must_be_instance_of Enumerize::Value
772+
expect(result.to_s).must_equal 'active'
773+
expect(result.value).must_equal 1
774+
end
775+
776+
it 'Type#cast returns Enumerize::Value for valid string keys' do
777+
type = User.type_for_attribute('status')
778+
result = type.cast('active')
779+
780+
expect(result).must_be_instance_of Enumerize::Value
781+
expect(result.to_s).must_equal 'active'
782+
expect(result.value).must_equal 1
783+
end
784+
785+
it 'Type#cast returns nil for invalid values' do
786+
type = User.type_for_attribute('status')
787+
result = type.cast(:invalid)
788+
789+
expect(result).must_be_nil
790+
end
791+
792+
it 'Type#cast preserves existing Enumerize::Value objects' do
793+
type = User.type_for_attribute('status')
794+
original_value = User.enumerized_attributes[:status].find_value(:active)
795+
result = type.cast(original_value)
796+
797+
expect(result).must_equal original_value
798+
end
799+
800+
it 'insert_all with mixed value types works correctly' do
801+
User.delete_all
802+
803+
User.insert_all([
804+
{ sex: :male, status: :active },
805+
{ sex: 'female', status: 2 },
806+
{ sex: :male, status: 'blocked' }
807+
])
808+
809+
users = User.all.to_a
810+
expect(users.size).must_equal 3
811+
812+
expect(users[0].sex).must_equal 'male'
813+
expect(users[0].status).must_equal 'active'
814+
815+
expect(users[1].sex).must_equal 'female'
816+
expect(users[1].status).must_equal 'blocked'
817+
818+
expect(users[2].sex).must_equal 'male'
819+
expect(users[2].status).must_equal 'blocked'
820+
end
821+
822+
it 'insert_all handles invalid enum values gracefully' do
823+
User.delete_all
824+
825+
User.insert_all([
826+
{ sex: :invalid_sex, status: 999 } # Use invalid integer for status
827+
])
828+
829+
user = User.first
830+
expect(user.sex).must_be_nil
831+
expect(user.status).must_be_nil
832+
end
833+
834+
it 'upsert_all with different value formats works correctly' do
835+
User.delete_all
836+
837+
User.upsert_all([
838+
{ id: 1, sex: :male, status: 1 },
839+
{ id: 2, sex: 'female', status: :blocked }
840+
])
841+
842+
User.upsert_all([
843+
{ id: 1, sex: 'male', status: :active },
844+
{ id: 3, sex: :female, status: 2 }
845+
])
846+
847+
users = User.order(:id).to_a
848+
expect(users.size).must_equal 3
849+
850+
expect(users[0].sex).must_equal 'male'
851+
expect(users[0].status).must_equal 'active'
852+
853+
expect(users[1].sex).must_equal 'female'
854+
expect(users[1].status).must_equal 'blocked'
855+
856+
expect(users[2].sex).must_equal 'female'
857+
expect(users[2].status).must_equal 'blocked'
858+
end
757859
end

0 commit comments

Comments
 (0)