Skip to content

Support Rails normalizes method and fix empty string handling consistency#471

Closed
snaka wants to merge 2 commits intobrainspec:masterfrom
snaka:fix/normalize
Closed

Support Rails normalizes method and fix empty string handling consistency#471
snaka wants to merge 2 commits intobrainspec:masterfrom
snaka:fix/normalize

Conversation

@snaka
Copy link
Copy Markdown
Contributor

@snaka snaka commented Sep 22, 2025

Motivation / Background

This Pull Request enhances Enumerize to properly support Rails 7.1+ normalizes method and addresses empty string handling consistency across ORMs.

Detail

Rails Normalizes Integration

Rails 7.1 introduced the normalizes method for attribute normalization:

normalizes :locale, with: ->(value) { value.downcase.strip.presence }
enumerize :locale, in: [:de, :en, :pl]

The existing Type#cast and Type#serialize methods didn't properly delegate to subtypes for normalization. This prevented normalized values from being correctly matched against enum values, causing enum validation to fail even when the normalized value was valid.

For example, with the above configuration, setting locale = " DE " should normalize to "de" and match the :de enum value, but this wasn't working properly.

Fixes #459

Empty String Handling Consistency

Additionally, Enumerize exhibited inconsistent behavior when handling empty strings across various ORMs:

  • ActiveRecord: Implicitly converted empty strings to nil
  • Sequel/Others: Stored empty strings as ""

This inconsistency led to failing tests, specifically: "stores nil when empty string assigned" in SequelTest.

Solution

1. Enhanced Type Casting for Normalization Support

Updated lib/enumerize/activerecord.rb to properly handle value transformation through subtypes:

def cast(value)
  return value if value.is_a?(::Enumerize::Value)

  # First try direct lookup
  enumerize_value = @attr.find_value(value)

  # If not found and we have a subtype, delegate for transformation (e.g., normalization)
  if !enumerize_value && @subtype
    casted_value = @subtype.cast(value)
    enumerize_value = @attr.find_value(casted_value)
  end

  enumerize_value
end

Similar logic was applied to the serialize method to ensure consistent behavior during database operations.

2. Explicit Empty String Conversion

Modified lib/enumerize/attribute.rb to explicitly convert empty strings to nil in the attribute setter:

def #{name}=(new_value)
  allowed_value = self.class.enumerized_attributes[:#{name}].find_value(new_value)

  value_to_assign = if allowed_value
    allowed_value.value
  elsif new_value == ''
    nil  # Explicit conversion for consistency across ORMs
  else
    new_value
  end
  # ...
end

This ensures consistent behavior across all ORMs, eliminating the discrepancy between ActiveRecord and other ORMs like Sequel.

Add test cases to verify that enumerized attributes work correctly
with Rails 7.1+ normalizes method for data normalization.
Fix issue where empty strings were not being stored as nil in Sequel and other ORMs.
This ensures consistent behavior across all ORMs by converting empty strings to nil,
matching ActiveRecord's behavior.

- Convert empty strings to nil in attribute setter
- Fix failing SequelTest for empty string assignment
- Ensure consistent behavior between ActiveRecord and Sequel
@snaka
Copy link
Copy Markdown
Contributor Author

snaka commented Oct 2, 2025

I feel like this PR has become a bit hard to understand, so I'll fix it again on a separate branch.

@snaka snaka closed this Oct 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incompatible with ActiveRecord normalizes

1 participant