Column default incorrectly showing as `NULL`
Closed this issue · 3 comments
I have two models, both with a state
field, both with a default value. annotate_models
correctly reflected the default for both, until I ran a migration with change_column :table, :col, :type, default: 'new-default'
. Now, the changed default is documented as default(NULL), not null
(which doesn't even make sense) and I can't get it to show the correct default anymore, even after tearing down DB and re-running all migrations.
This is a pretty brand new app, undeployed, not even any controllers yet... I have no theory for how/why this is happening.
Migration:
class ChangeIdentityStateDefault < ActiveRecord::Migration[7.0]
def up
change_column :identities, :state, :text, default: 'submitted'
Identity.where(state: 'pending_review').update_all(state: 'submitted')
end
def down
change_column :identities, :state, :text, default: 'pending_review'
Identity.where(state: 'submitted').update_all(state: 'pending_review')
end
end
psql
of both tables:
beekeeper=# \d identities
Table "public.identities"
Column | Type | Collation | Nullable | Default
--------------+------+-----------+----------+-------------------
id | uuid | | not null | gen_random_uuid()
callback_url | text | | not null |
state | text | | not null | 'submitted'::text
Indexes:
"identities_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "identifications" CONSTRAINT "fk_rails_403e5f005a" FOREIGN KEY (identity_id) REFERENCES identities(id)
beekeeper=# \d identifications
Table "public.identifications"
Column | Type | Collation | Nullable | Default
------------------+-----------------------------+-----------+----------+-------------------
id | uuid | | not null | gen_random_uuid()
identity_id | uuid | | not null |
state | text | | not null | 'submitted'::text
checks | jsonb | | |
claimed_age | text | | not null |
submitter_notes | text | | |
submitted_at | timestamp without time zone | | |
created_at | timestamp(6) with time zone | | not null |
updated_at | timestamp(6) with time zone | | not null |
expires_at | timestamp(6) with time zone | | |
rejection_reason | text | | |
Indexes:
"identifications_pkey" PRIMARY KEY, btree (id)
"index_identifications_on_identity_id" btree (identity_id)
Foreign-key constraints:
"fk_rails_403e5f005a" FOREIGN KEY (identity_id) REFERENCES identities(id)
Referenced by:
TABLE "identification_state_transitions" CONSTRAINT "fk_rails_7c40242644" FOREIGN KEY (identification_id) REFERENCES identifications(id)
The comments generated since running the migration:
# == Schema Information
#
# Table name: identities
#
# id :uuid not null, primary key
# callback_url :text not null
# state :text default(NULL), not null
#
class Identity < ApplicationRecord
# == Schema Information
#
# Table name: identifications
#
# id :uuid not null, primary key
# checks :jsonb
# claimed_age :text not null
# expires_at :datetime
# rejection_reason :text
# state :text default("submitted"), not null
# submitted_at :timestamp
# submitter_notes :text
# created_at :datetime not null
# updated_at :datetime not null
# identity_id :uuid not null, indexed
#
# Indexes
#
# index_identifications_on_identity_id (identity_id)
#
# Foreign Keys
#
# fk_rails_... (identity_id => identities.id)
#
class Identification < ApplicationRecord
Commands
The default is incorrect (and overwrites manual corrections) both when running a post-migration rake hook and when running bundle exec annotate
directly.
Version
ruby 3.0.3p157 (2021-11-24 revision 3fb7d2cadc) [aarch64-linux-musl]
- Rails 7.0.0
annotate
3.2.0
Strangely, if I run rails db:rollback
the comment shows the restored default string DEFAULT("pending_review")
:
bash-5.1# rails db:rollback
== 20220215062631 ChangeIdentityStateDefault: reverting =======================
-- change_column(:identities, :state, :text, {:default=>"pending_review"})
-> 0.0041s
== 20220215062631 ChangeIdentityStateDefault: reverted (0.0562s) ==============
Annotated (1): app/models/identity.rb
# == Schema Information
#
# Table name: identities
#
# id :uuid not null, primary key
# callback_url :text not null
# state :text default("pending_review"), not null
#
class Identity < ApplicationRecord
Further, if I edit the migration to have any default string other than 'pending_review
, annotate documents the default as NULL
and if I change even the up
part to re-set the default as pending_review
it documents it correctly.
This makes no sense to me...
Oh damn... I think it's related to #768 or #677 or something in those code paths, but that at least allowed me to fix it.
The issue was that an enum
on the model hadn't yet been changed to include the new value. I'm assuming there is some code in annotate to map the enum
values to/from the raw DB value and this was returning a nil
in Ruby, which was mis-described as a NULL
from DB.