Skip to content

Make Place.user_id NOT NULL — complete migration period - #3358

Open
Freika wants to merge 4 commits into
devfrom
chore/place-user-id-not-null
Open

Make Place.user_id NOT NULL — complete migration period#3358
Freika wants to merge 4 commits into
devfrom
chore/place-user-id-not-null

Conversation

@Freika

@Freika Freika commented Aug 15, 2026

Copy link
Copy Markdown
Owner

Summary

Closes out the migration window opened in #2494 (April 2026). Place.user_id was left nullable during the 1.7.0 release so the backfill job could run without blocking deploys. The backfill migration (May 2026, BackfillPlacesUserIdJob) has had ~3 months to process; this PR enforces the constraint.

Changes:

  • app/models/place.rb — removes optional: true from belongs_to :user (the comment said "Optional until Stage 2 NOT NULL"; this is Stage 2)
  • db/migrate/20260815100000_add_places_user_id_not_null_check.rb — adds a check constraint with validate: false (no table lock; follows the same pattern as AddDatabaseUsersConstraints)
  • db/migrate/20260815100001_validate_places_user_id_not_null.rb — validates the constraint, calls change_column_null :places, :user_id, false, then removes the temporary check constraint
  • app/services/visits/create.rb — drops the now-redundant .where(places: { user_id: user.id }) clause from find_existing_place; the column-level NOT NULL plus belongs_to ownership provides the same guarantee

⚠️ Pre-merge gate

Before merging, confirm that no rows have a null user_id in the environment you're deploying to:

rails runner 'count = Place.where(user_id: nil).count; puts "NULL user_id places: #{count}"; exit(1) if count > 0'

If the count is non-zero the backfill job is still in progress (or stalled). Do not merge until it's zero — ValidatePlacesUserIdNotNull will fail at deploy time otherwise, which rolls back the migration and leaves the schema inconsistent.

The check constraint migration (20260815100000) is safe to run at any time — it only adds a not-yet-validated constraint and takes no lock. The validate migration (20260815100001) is the one that enforces the constraint and will fail fast if any null rows remain.

Migration sequence on deploy

rails db:migrate   # runs 20260815100000 (instant, no lock)
                   # then 20260815100001 (validate + NOT NULL)

Both migrations run inside the normal deploy. No separate rake task needed.

Test plan

  • Confirm Place.where(user_id: nil).count returns 0 in staging/production before merging
  • Run rails db:migrate against a staging database and verify both migrations succeed
  • Verify Place.new(name: 'X', lonlat: ...).save raises ActiveRecord::RecordInvalid (user required)
  • Verify Visits::Create.new(user, params).call still creates/finds places correctly

Generated by Claude Code

Removes the `optional: true` guard added during the PR #2494 migration
window. The backfill job (BackfillPlacesUserIdJob) enqueued by the May
2026 migration has had time to run; all places should now carry a
user_id.

Uses the Strong Migrations two-step pattern (check constraint with
validate:false → validate → change_column_null) to avoid a full-table
lock. Also drops the now-redundant `.where(places: { user_id: user.id })`
from Visits::Create#find_existing_place — the NOT NULL column plus
belongs_to ownership provides the same guarantee.

⚠️  Merge only after confirming zero null user_ids in production:
    rails runner 'puts Place.where(user_id: nil).count'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsoH1qvvJ6FKzuqNCDXYSG
@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3354d0d4-d7da-4bb9-94ba-ddeb5653a4bc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

claude added 3 commits August 15, 2026 09:06
Bumps schema version to 2026_08_15_100001 and marks places.user_id
null: false, reflecting what the two new migrations produce. Without
this, CI's db:schema:load leaves both migrations as "pending" and
RSpec aborts with ActiveRecord::PendingMigrationError.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsoH1qvvJ6FKzuqNCDXYSG
- Add user_id to upsert_all update_attributes in ReverseGeocoding::Places::FetchData
  to avoid PostgreSQL rejecting the INSERT side of ON CONFLICT DO UPDATE
- Simplify backfill job specs to no-op assertions; ownerless places
  can no longer be created at the DB level
- Replace orphan_cleanup_job nil-pass tests (3 tests → 1 no-op test)
- Fix full_history_redetect_job_spec: change user:nil to user:create(:user)
- Delete backfill_user_id_on_places_spec (migration period is over;
  update_columns(user_id: nil) now fails the NOT NULL constraint)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsoH1qvvJ6FKzuqNCDXYSG
Visits::Create#find_existing_place was missing the place-level ownership
filter, which let user B's new visit attach to user A's place when user B
had a prior visit to user A's place at the same coordinates.
Restoring .where(user: user) on the Place side prevents this cross-user
place leak.

fetch_data_spec: build(:place) with FactoryBot 5+ propagates build strategy
to associations, leaving user_id nil. Changed to build(:place, user: create(:user))
so insert_all receives a valid user_id.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsoH1qvvJ6FKzuqNCDXYSG
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.

2 participants