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:
-
Root model app/models/address.rb:
class Address < ApplicationRecord # table: addresses
end
-
Pack model packs/contacts/app/public/models/address.rb:
module Contacts
class Address < ApplicationRecord # table: contact_addresses
self.table_name = "contact_addresses"
end
end
-
A test file packs/contacts/test/models/address_test.rb (any content).
-
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)
Steps to reproduce the problem
In a Rails app organized with packwerk packs (
packs/*), create two modelsthat share a file basename but map to different tables:
Root model
app/models/address.rb:Pack model
packs/contacts/app/public/models/address.rb:A test file
packs/contacts/test/models/address_test.rb(any content).With the configuration below, run
bin/annotaterb modelstwice, orbin/annotaterb models --frozenonce.Expected behavior
Each model annotates only its own related files, and
--frozenpasses whenthe 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 rootAddresswrites the
addressesschema,Contacts::Addresswritescontact_addresses—and the last writer wins. A plain run reports the same file annotated twice:
And
--frozenalways fails on whichever model "lost", even though nothing isstale:
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#getexpands every pattern for everyroot_direntry, not just the root the current model file lives under, so aroot-level model also generates patterns like
packs/*/test/models/%MODEL_NAME%_test.rb.ModelAnnotator::FileNameResolver.callsubstitutes only name placeholders.Both models here resolve to
address(both sit directly in amodel_dir, soeven
%MODEL_NAME%carries no namespace), producing identical globs.ModelAnnotator::RelatedFilesListBuilder#related_files_for_patternthenDir.globs those patterns with no notion of where the model came from, soboth 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.ymlconfiguration(Reproduces with the built-in test/fixture patterns as well — the
additional_file_patternsentry is not required for the collision.)Environment
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)