Skip to content

4.24.0: enum-backed integer columns annotate raw default(0) instead of default("label") #373

Description

@laiskajoonas

Summary

Since 4.24.0, annotations for integer columns backing a Rails enum render the raw
integer default instead of the enum label. Regenerating annotations on an unchanged
schema produces a diff on every such column:

-#  signing_provider_service :integer          default("idnow"), not null
+#  signing_provider_service :integer          default(0), not null

-#  base_portfolio           :integer          default("classic"), not null
+#  base_portfolio           :integer          default(0), not null

-#  role                     :integer          default("user"), not null
+#  role                     :integer          default(0), not null

4.23.0 produced the label form. In our app this touched ~45 files (models, serializers
and factories) purely from the gem bump, which fails the CI job that runs
rails db:migrate:reset and asserts a clean working tree.

Environment

  • annotaterb 4.24.0 (4.23.0 is fine)
  • Rails 8.1, Ruby 4.0
  • PostgreSQL 17

Cause

I believe this is unintended fallout from #358 ("Respect DB column defaults over
attribute :foo, default: X overrides"), which rewrote ModelWrapper#column_defaults:

@column_defaults ||= @klass.column_defaults.each_with_object({}) do |(name, value), result|
  column = @klass.columns_hash[name]
  schema_value = schema_default_for(column)
  result[name] = (value == schema_value) ? value : schema_value
end

Model#column_defaults casts the DB default through the attribute type, so for an
enum column it yields the label ("idnow"). schema_default_for casts through the
column type, yielding 0. They differ, so the branch treats it as an
attribute-level override and substitutes the raw schema value — but for enums the
difference is inherent to how ActiveRecord::Enum decorates the attribute type, not
an attribute ... default: override.

Declaring an enum is enough to trigger it:

class AccountRequest < ApplicationRecord
  enum :signing_provider_service, {idnow: 0, fidentity: 1}, prefix: true
end
# 4.23.0: default("idnow")
# 4.24.0: default(0)

Expected

Enum-backed columns keep rendering their symbolic default, since that is the
meaningful value for a reader — while attribute :foo, default: X overrides continue
to show the DB schema default as #358 intended.

A possible discriminator: skip the substitution when the attribute type is an
ActiveRecord::Enum::EnumType (or, more generally, when the attribute type is a
decorator around the column's cast type rather than an independent default), instead
of inferring an override from value inequality alone.

I'm happy to put up a PR if you agree with that direction.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions