Skip to content

Related files are matched globally by unqualified model name, so models with the same basename in different packs overwrite each other's annotations (breaks --frozen) #367

Description

@tomyyn

Steps to reproduce the problem

In a Rails app organized with packwerk packs (packs/*), create two models
that share a file basename but map to different tables:

  1. Root model app/models/address.rb:

    class Address < ApplicationRecord # table: addresses
    end
  2. Pack model packs/contacts/app/public/models/address.rb:

    module Contacts
      class Address < ApplicationRecord # table: contact_addresses
        self.table_name = "contact_addresses"
      end
    end
  3. A test file packs/contacts/test/models/address_test.rb (any content).

  4. With the configuration below, run bin/annotaterb models twice, or
    bin/annotaterb models --frozen once.

Expected behavior

Each model annotates only its own related files, and --frozen passes when
the working tree is fully up to date.

Actual behavior

Both models claim the same related file. Each run, both write their own
schema into packs/contacts/test/models/address_test.rb — the root Address
writes the addresses schema, Contacts::Address writes contact_addresses
and the last writer wins. A plain run reports the same file annotated twice:

Annotating models
Annotated (2): packs/contacts/test/models/address_test.rb, packs/contacts/test/models/address_test.rb

And --frozen always fails on whichever model "lost", even though nothing is
stale:

AnnotateRb error. packs/contacts/test/models/address_test.rb needs to be updated, but annotaterb was run with `--frozen`.

This appears to come from related files being resolved purely by name, globbed
across the whole project (unlike model files themselves, which are annotated
file → class and are unambiguous):

  • ModelAnnotator::PatternGetter#get expands every pattern for every
    root_dir entry, not just the root the current model file lives under, so a
    root-level model also generates patterns like
    packs/*/test/models/%MODEL_NAME%_test.rb.
  • ModelAnnotator::FileNameResolver.call substitutes only name placeholders.
    Both models here resolve to address (both sit directly in a model_dir, so
    even %MODEL_NAME% carries no namespace), producing identical globs.
  • ModelAnnotator::RelatedFilesListBuilder#related_files_for_pattern then
    Dir.globs those patterns with no notion of where the model came from, so
    both models match the same existing test file.

The name-based matching implicitly assumes model basenames are unique across
the project — true for flat Rails apps, but not for apps organized into packs.

Your .annotaterb.yml configuration

:exclude_tests: false
:root_dir:
  - ''
  - packs/*
:model_dir:
  - app/models
  - packs/*/app/models
  - packs/*/app/public/models
:additional_file_patterns:
  - "packs/*/test/models/%MODEL_NAME_WITHOUT_NS%_test.rb"

(Reproduces with the built-in test/fixture patterns as well — the
additional_file_patterns entry is not required for the collision.)

Environment

  • AnnotateRb version: 4.22.0
  • Ruby version: 4.0.3
  • Ruby on Rails version: 8.1.3
  • Database adapter (e.g., sqlite3, pg, mysql2): pg
  • Database adapter version: 1.6.3

Issue title: Related files are matched globally by unqualified model name, so models with the same basename in different packs
overwrite each other's annotations (breaks --frozen)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions