rails-ai-context 5.36.4 → 5.36.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +732 -612
- data/docs/COMPATIBILITY.md +42 -29
- data/docs/GUIDE.md +2 -1
- data/docs/INTROSPECTORS.md +6 -6
- data/docs/TOOLS.md +3 -2
- data/lib/rails_ai_context/action_outcome.rb +2 -2
- data/lib/rails_ai_context/concern_macros.rb +11 -8
- data/lib/rails_ai_context/concern_membership.rb +85 -9
- data/lib/rails_ai_context/concern_paths.rb +1 -1
- data/lib/rails_ai_context/configuration.rb +1 -1
- data/lib/rails_ai_context/data/docs/index.json +0 -1
- data/lib/rails_ai_context/fingerprinter.rb +1 -1
- data/lib/rails_ai_context/introspector.rb +1 -2
- data/lib/rails_ai_context/introspectors/action_presence.rb +1 -1
- data/lib/rails_ai_context/introspectors/action_source.rb +4 -4
- data/lib/rails_ai_context/introspectors/controller_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/convention_introspector.rb +4 -3
- data/lib/rails_ai_context/introspectors/declared_constant.rb +11 -4
- data/lib/rails_ai_context/introspectors/env_references.rb +1 -1
- data/lib/rails_ai_context/introspectors/i18n_introspector.rb +4 -4
- data/lib/rails_ai_context/introspectors/job_introspector.rb +2 -2
- data/lib/rails_ai_context/introspectors/listeners/associations_listener.rb +5 -2
- data/lib/rails_ai_context/introspectors/listeners/enums_listener.rb +6 -2
- data/lib/rails_ai_context/introspectors/listeners/generic_macro_listener.rb +4 -1
- data/lib/rails_ai_context/introspectors/listeners/mixins_listener.rb +0 -26
- data/lib/rails_ai_context/introspectors/listeners/routes_dsl_listener.rb +3 -3
- data/lib/rails_ai_context/introspectors/listeners/validations_listener.rb +4 -2
- data/lib/rails_ai_context/introspectors/model_introspector.rb +167 -43
- data/lib/rails_ai_context/introspectors/performance_introspector.rb +55 -33
- data/lib/rails_ai_context/introspectors/route_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/service_classes.rb +1 -1
- data/lib/rails_ai_context/introspectors/source_scan.rb +1 -1
- data/lib/rails_ai_context/introspectors/table_name.rb +18 -6
- data/lib/rails_ai_context/introspectors/test_introspector.rb +2 -2
- data/lib/rails_ai_context/path_resolver.rb +1 -1
- data/lib/rails_ai_context/redaction.rb +1 -1
- data/lib/rails_ai_context/route_coverage.rb +1 -1
- data/lib/rails_ai_context/tools/analyze_feature.rb +64 -68
- data/lib/rails_ai_context/tools/dependency_graph.rb +5 -9
- data/lib/rails_ai_context/tools/diagnose.rb +12 -6
- data/lib/rails_ai_context/tools/generate_test.rb +2 -2
- data/lib/rails_ai_context/tools/get_concern.rb +1 -1
- data/lib/rails_ai_context/tools/get_controllers.rb +1 -1
- data/lib/rails_ai_context/tools/get_edit_context.rb +19 -11
- data/lib/rails_ai_context/tools/get_i18n.rb +1 -1
- data/lib/rails_ai_context/tools/get_service_pattern.rb +1 -1
- data/lib/rails_ai_context/tools/get_test_info.rb +1 -1
- data/lib/rails_ai_context/tools/migration_advisor.rb +160 -55
- data/lib/rails_ai_context/tools/validate_semantics.rb +1 -1
- data/lib/rails_ai_context/version.rb +1 -1
- metadata +1 -1
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,141 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [5.36.6] - 2026-10-22
|
|
9
|
+
|
|
10
|
+
### Removed
|
|
11
|
+
|
|
12
|
+
- **The mixin reader no longer reads `prepend_mod_with` and its siblings.**
|
|
13
|
+
An app defines those macros, not Rails, and each app picks its own module
|
|
14
|
+
namespace, while the listener assumed one application's `EE::` and `JH::`.
|
|
15
|
+
A class that calls them now lists only the mixins it writes with `include`,
|
|
16
|
+
`prepend` and `extend`.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **`rails_migration_advisor` puts an association in the class its table
|
|
21
|
+
belongs to** (#500). Every STI subclass reads its base's table, so the
|
|
22
|
+
`belongs_to` went to whichever subclass sorted first (`CallForEvidence`, not
|
|
23
|
+
`Edition`) and the base and every other subclass had no association. The
|
|
24
|
+
`has_many` on the other end had the same problem: a `users` table owned by
|
|
25
|
+
`Principal` got its subclass `AnonymousUser`. A subclass that sets a table
|
|
26
|
+
of its own still owns that one, and the Strong Migrations `ignored_columns`
|
|
27
|
+
step for `remove_column` names the base too, where it named the subclass
|
|
28
|
+
that shares the table's name. A base that raised while the app was read is
|
|
29
|
+
still the owner: its entry now keeps its file, table and parent, as the
|
|
30
|
+
static tier's now does. A subclass whose file the booted app cannot load
|
|
31
|
+
keeps its parent too, so it is not read as a second owner.
|
|
32
|
+
- **`rails_migration_advisor` writes the `class_name` Rails needs to reach the
|
|
33
|
+
class that owns the table** (#502). The `has_many` had none, so
|
|
34
|
+
`has_many :budget_phases` looked for a `BudgetPhase` where `Budget::Phase`
|
|
35
|
+
reads the table. Rails looks a name up in the model's namespace first, so
|
|
36
|
+
`has_many :comments` on `Admin::Post` now says `class_name: "::Comment"`
|
|
37
|
+
where an `Admin::Comment` would shadow it, and a name that already reaches
|
|
38
|
+
its class gets none. The static tier cannot see the app's acronyms, so there
|
|
39
|
+
`ai_match` still names `AIMatch`: a redundant `class_name` runs, and a
|
|
40
|
+
missing one raises.
|
|
41
|
+
- **A model file that declares a second model lists both, each with its own
|
|
42
|
+
declarations** (#503). On the static tier the second class was missing, and
|
|
43
|
+
its associations, validations and enums were the first class's:
|
|
44
|
+
`PaymentRefund` beside `Payment` in payment.rb answered "No matches" while
|
|
45
|
+
`Payment` gained a `belongs_to :payment`. Each class now takes its table
|
|
46
|
+
from its own name, an abstract class beside a model no longer drops the
|
|
47
|
+
model, and a model assigned with `Class.new` is listed too. A class that
|
|
48
|
+
shares a file is listed when it reaches a record base through the app's own
|
|
49
|
+
classes; one on a gem's base is not guessed at. A model's own file still
|
|
50
|
+
wins over another file that declares the same class, and what that other
|
|
51
|
+
file adds is not merged in.
|
|
52
|
+
|
|
53
|
+
A module nested in the model's file and included by it is read as the
|
|
54
|
+
model's: its scopes and macros were missing, and its associations,
|
|
55
|
+
validations and enums had been counted only because every line of the file
|
|
56
|
+
was. It is named as Ruby reaches it, `Payment::Trackable` where the static
|
|
57
|
+
tier said `Trackable`.
|
|
58
|
+
|
|
59
|
+
The booted tier gave each class in the file the validations of all of them.
|
|
60
|
+
A Mongoid document beside another was missing, and the first took its
|
|
61
|
+
fields, associations and validations. `rails_performance_check` counted the
|
|
62
|
+
next class's `has_many` as the first one's. Each now reads its own
|
|
63
|
+
declarations and those of the modules in its file that it includes.
|
|
64
|
+
- **Two models whose names differ only in case stay apart.** An app with both
|
|
65
|
+
`Checkin` and `CheckIn` had every association to either drawn to one of them
|
|
66
|
+
in `rails_dependency_graph`, and named that one in `rails_analyze_feature`,
|
|
67
|
+
`rails_get_controllers`, `rails_get_turbo_map`, `rails_validate`,
|
|
68
|
+
`rails_performance_check` and `rails_migration_advisor`. An exact spelling
|
|
69
|
+
now wins over a match that ignores case, even one in a nearer namespace. The
|
|
70
|
+
lookup behind them builds its index of model names once per run, where it
|
|
71
|
+
rebuilt it for every association it resolved.
|
|
72
|
+
- **`rails_analyze_feature` finds a model by its class name.** A feature such
|
|
73
|
+
as `AIMatchResult` was downcased into one word, `aimatchresult`, which no
|
|
74
|
+
name's words match, so the answer was "No matches" and then suggested that
|
|
75
|
+
same name. A class name now matches split at its capitals, which
|
|
76
|
+
`AIMatchResult` needs, and also as written, which a name joined by an app
|
|
77
|
+
acronym needs (`Token::ICal` in `token/ical.rb`). A model's test is looked
|
|
78
|
+
for by the name of its own file, which carries the app's inflection, so
|
|
79
|
+
`Token::ICal` no longer reads as untested beside its `ical_token_spec.rb`.
|
|
80
|
+
- **`rails_performance_check` names a namespaced model's `Model.all` by its
|
|
81
|
+
qualified name.** `Admin::Post.all` was reported as `Post.all`, and a
|
|
82
|
+
`Post.all` that reaches no model, or reaches a class that is not one, was
|
|
83
|
+
reported too. The constant is now resolved through the controller's
|
|
84
|
+
`Module.nesting`, so a compact `class Admin::PostsController` reaches
|
|
85
|
+
`::Post` where a nested one reaches `Admin::Post`.
|
|
86
|
+
|
|
87
|
+
## [5.36.5] - 2026-10-19
|
|
88
|
+
|
|
89
|
+
### Added
|
|
90
|
+
|
|
91
|
+
- **`rails_get_edit_context` takes a `line`.** It shows `context_lines` either
|
|
92
|
+
side of that line, so a line from a stack trace needs no text to search for.
|
|
93
|
+
`near` is needed only without one.
|
|
94
|
+
|
|
95
|
+
### Fixed
|
|
96
|
+
|
|
97
|
+
- **`rails_diagnose` shows the code around the line it was given** (#495). It
|
|
98
|
+
searched for the method name and opened on its first mention, a comment
|
|
99
|
+
included, so the failing line was missing from the code shown in 18 of 30
|
|
100
|
+
real calls on one app. It also reads a setter's name (`title=`) and an index
|
|
101
|
+
call's (`[]`), and names their nil receiver.
|
|
102
|
+
- **`rails_migration_advisor` points a foreign key at the key of a table not
|
|
103
|
+
keyed by `id`** (#496): `foreign_key: { primary_key: :bid }`, where
|
|
104
|
+
`foreign_key: true` made PostgreSQL refuse the migration. A table with a
|
|
105
|
+
composite primary key, and a `t.references` table the schema lacks, get no
|
|
106
|
+
foreign key and a line saying why.
|
|
107
|
+
- **`rails_migration_advisor` types a reference the way the key it points at
|
|
108
|
+
is typed** (#501). A reference to an `integer` key gets `type: :integer`
|
|
109
|
+
and the key's `limit:` when it has one, and on MySQL one to an `unsigned`
|
|
110
|
+
key gets `unsigned: true`. It wrote Rails' default signed bigint, and MySQL
|
|
111
|
+
and MariaDB refuse a foreign key whose column differs from the key, so the
|
|
112
|
+
migration failed. PostgreSQL, which accepted the bigint, now gets the
|
|
113
|
+
matching type too. A `serial` key gives `integer` and a `smallserial` one
|
|
114
|
+
`smallint`, where the reference used to get a sequence of its own that
|
|
115
|
+
filled it with a made-up owner. SQLite, where every key is an integer,
|
|
116
|
+
still gets no type.
|
|
117
|
+
- **`rails_analyze_feature` lists every service, view and spec whose name
|
|
118
|
+
matches** (#497), wherever it sorts. Only the files it reads stop at 500 per
|
|
119
|
+
directory, and the closing note says which directory past them was matched
|
|
120
|
+
by name alone. The test gap list reads the name of every test, so it is no
|
|
121
|
+
longer left out on a suite of more than 500.
|
|
122
|
+
- **`conventions` counts the TypeScript, JSX and Vue files of
|
|
123
|
+
`app/javascript`** (#498), and the `.ruby` templates of `app/views`. One
|
|
124
|
+
app's `app/javascript` read 78 files; it holds 922. An edit to one of those
|
|
125
|
+
files, or to a `.jbuilder` or `.builder` view, now refreshes the cached
|
|
126
|
+
answers, as one to a `.js` or `.erb` file did.
|
|
127
|
+
- **`rails_migration_advisor`'s Strong Migrations warning for `add_association`
|
|
128
|
+
rewrites the `add_reference` it wrote** (#499). It named the reference as the
|
|
129
|
+
table (`add_foreign_key :listings, :owner`) and added a second key after the
|
|
130
|
+
migration had added one. On PostgreSQL it now gives the reference with
|
|
131
|
+
`disable_ddl_transaction!` and a concurrent index, the key in its own
|
|
132
|
+
`add_foreign_key ... validate: false` (strong_migrations before 2.5.2 flags any
|
|
133
|
+
foreign key inside `add_reference`), then `validate_foreign_key` with
|
|
134
|
+
the real table and column. On MySQL it moves the key to a separate
|
|
135
|
+
migration, as strong_migrations 2.7 and later require; an older locked version,
|
|
136
|
+
which does not check a MySQL key, gets no warning. A reference with no
|
|
137
|
+
foreign key gets only the concurrent index on PostgreSQL and nothing on
|
|
138
|
+
MySQL, and SQLite, which strong_migrations does not check, gets nothing. A
|
|
139
|
+
reference whose column already exists gets no warning, since its migration
|
|
140
|
+
fails first, and an undecided database gets one line when the reference has
|
|
141
|
+
a key.
|
|
142
|
+
|
|
8
143
|
## [5.36.4] - 2026-10-16
|
|
9
144
|
|
|
10
145
|
### Added
|
|
@@ -1433,15 +1568,14 @@ Ten tools that each answered one section of the app's configuration leave
|
|
|
1433
1568
|
|
|
1434
1569
|
### Added
|
|
1435
1570
|
|
|
1436
|
-
- **A static model answer names the plugin patch modules it did not
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
`
|
|
1444
|
-
module it did not read.
|
|
1571
|
+
- **A static model answer names the plugin patch modules it did not read.** An
|
|
1572
|
+
app plugin that adds to a core model through its own DSL (`patches
|
|
1573
|
+
%i[WorkPackage]`, which includes `<Plugin>::Patches::WorkPackagePatch` in
|
|
1574
|
+
`to_prepare`) gave a `rails_get_model_details` answer with none of what the
|
|
1575
|
+
patches add and no sign anything was left out. A module named
|
|
1576
|
+
`*::Patches::<Model>Patch` in the app's, its code roots' or its in-repo
|
|
1577
|
+
gems' `lib` is now named in one `[UNAVAILABLE]` line, the way
|
|
1578
|
+
`rails_get_controllers` names an included module it did not read.
|
|
1445
1579
|
- **`rails_get_model_details` names each `has_attached_file` attachment
|
|
1446
1580
|
under Macros.**
|
|
1447
1581
|
- **The controller listing marks `permit!` and names a parent's strong
|
|
@@ -1480,23 +1614,23 @@ Ten tools that each answered one section of the app's configuration leave
|
|
|
1480
1614
|
*_params` was listed, so a method that builds associations counted while
|
|
1481
1615
|
`article_params_json`, which calls `permit`, did not. A method named
|
|
1482
1616
|
`*_params` now counts when it calls `permit`, `permit!` or `expect` on
|
|
1483
|
-
params (directly, through a local or an ivar, on each item a block walks,
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
(`PermittedAttributes::Enterprise.new(params).call`); a `before_action`
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1617
|
+
params (directly, through a local or an ivar, on each item a block walks, or
|
|
1618
|
+
on `ActionController::Parameters.new`), or when what it returns comes from
|
|
1619
|
+
`super` (`super.merge(...)`), another strong params method (a call runs the
|
|
1620
|
+
nearest class's method of that name, so one the controller or a parent class
|
|
1621
|
+
defines counts whatever its name, such as `params_slice`; any other named
|
|
1622
|
+
`*_params` or `permitted_*`, such as a concern's or Pundit's
|
|
1623
|
+
`permitted_attributes(@post)`; and Devise's `devise_parameter_sanitizer`),
|
|
1624
|
+
or a call given the whole params
|
|
1625
|
+
(`PermittedAttributes::Enterprise.new(params).call`); a `before_action` that
|
|
1626
|
+
writes into another one's params is not one. A method of any other name
|
|
1627
|
+
counts only when it returns the permitted params themselves, through a
|
|
1628
|
+
local, an `if`, a `case` or an explicit `return`, so a helper that permits
|
|
1629
|
+
to build a URL, or to read one value, is left out. An action that permits
|
|
1630
|
+
inline is left out, a public `*_params` helper is kept, `permit!` on a
|
|
1631
|
+
`slice` lists the keys it slices, a computed filter (`settings_attributes:
|
|
1632
|
+
UserSettings.keys`) is shown as written, not as a scalar, and a key
|
|
1633
|
+
`require` computes is not printed as one.
|
|
1500
1634
|
|
|
1501
1635
|
### Fixed
|
|
1502
1636
|
|
|
@@ -1574,12 +1708,12 @@ Ten tools that each answered one section of the app's configuration leave
|
|
|
1574
1708
|
`print_result`.** The gem's rake helpers were top-level methods guarded by
|
|
1575
1709
|
`unless defined?`, so an app's `print_result` in lib/tasks ran instead and
|
|
1576
1710
|
the task aborted. They now live in a module.
|
|
1577
|
-
- **A byte-size label does not depend on the app's I18n.**
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1711
|
+
- **A byte-size label does not depend on the app's I18n.** `rails_read_logs`
|
|
1712
|
+
threw its whole answer away when an app's I18n backend raised after a failed
|
|
1713
|
+
boot (a backend that queries the database), and `rails_runtime_info` and
|
|
1714
|
+
`rails_get_test_info` could do the same.
|
|
1581
1715
|
- **The static fallback loads after a Bundler.setup that failed part way.**
|
|
1582
|
-
An app that activates a gem before `bundler/setup` (
|
|
1716
|
+
An app that activates a gem before `bundler/setup` (one app's
|
|
1583
1717
|
`require "sassc-embedded"`) at a version its lock does not pin made
|
|
1584
1718
|
Bundler raise after it had marked activesupport loaded but before it added
|
|
1585
1719
|
the load paths, so the standalone CLI and `serve` died with `cannot load
|
|
@@ -3525,16 +3659,17 @@ Ten tools that each answered one section of the app's configuration leave
|
|
|
3525
3659
|
directory such as `app/domain`, or under a path `config/application.rb` adds
|
|
3526
3660
|
to `eager_load_paths` or `autoload_paths`, when its superclass is a model. A
|
|
3527
3661
|
class there that is not a model is left out even when it does not load, and
|
|
3528
|
-
the lib subdirectories `autoload_lib(ignore:)` names are not read. The
|
|
3529
|
-
tier also finds models under a symlinked directory or file in
|
|
3530
|
-
that points inside the app, a model assigned
|
|
3531
|
-
with what its block declares (the
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3662
|
+
the lib subdirectories `autoload_lib(ignore:)` names are not read. The
|
|
3663
|
+
static tier also finds models under a symlinked directory or file in
|
|
3664
|
+
`app/models` that points inside the app, a model assigned
|
|
3665
|
+
`Class.new(ApplicationRecord)` with what its block declares (the
|
|
3666
|
+
`self.table_name` it sets included), and reads a job assigned
|
|
3667
|
+
`Class.new(ApplicationJob)`, or a class whose parent is such a base, the
|
|
3668
|
+
same way. An assignment counts only when the file's path names it, so an
|
|
3669
|
+
error constant such as `PaymentError = Class.new(StandardError)` is never
|
|
3670
|
+
listed as a service, serializer or model. The static tier also merges a
|
|
3671
|
+
module prepended after the class body (`Note.prepend(EE::Note)`,
|
|
3672
|
+
`prepend_mod_with("Note")`). (#344, #347, #413, #416)
|
|
3538
3673
|
- **Booted `model_details` no longer credits every model with a gem's base
|
|
3539
3674
|
mixins**, such as Kaminari's two modules and `page` and `default_per_page` on
|
|
3540
3675
|
the app's abstract base, or on a model that subclasses ActiveRecord::Base
|
|
@@ -3783,12 +3918,12 @@ Ten tools that each answered one section of the app's configuration leave
|
|
|
3783
3918
|
and its concerns' class methods, the last added first, then the same for
|
|
3784
3919
|
each base, then what every model has: class methods an initializer writes in
|
|
3785
3920
|
`ActiveRecord::Base.class_eval` or a reopened base, the file Rails loads
|
|
3786
|
-
later winning (
|
|
3921
|
+
later winning (a `validates_locale` that adds its `before_validation` to
|
|
3787
3922
|
Account, User and Course), then the modules mixed into the base. One an
|
|
3788
3923
|
initializer writes on `ApplicationRecord` replaces ApplicationRecord's own.
|
|
3789
|
-
A class's own class methods are its `def self.`, an `alias_method` in
|
|
3790
|
-
|
|
3791
|
-
|
|
3924
|
+
A class's own class methods are its `def self.`, an `alias_method` in `class
|
|
3925
|
+
<< self` (it runs what its original name ran at the alias), and those a
|
|
3926
|
+
Concern's `included` block or a hook writes on the class (`def self.x` or
|
|
3792
3927
|
`class << self`, in the block, in `base.class_eval` or in `class << base`),
|
|
3793
3928
|
each from its line where that code runs. A module counts from the line
|
|
3794
3929
|
adding it, where that line runs: an `extend`, `singleton_class.include` or
|
|
@@ -3807,12 +3942,12 @@ Ten tools that each answered one section of the app's configuration leave
|
|
|
3807
3942
|
like any concern: `include` runs its `included` block, `extend` its methods
|
|
3808
3943
|
and `self.extended` only. A module a plugin's lib loads by a glob `require`
|
|
3809
3944
|
is read from beside the file naming it, and from its definition rather than
|
|
3810
|
-
an empty namespace stub above it, so
|
|
3945
|
+
an empty namespace stub above it, so journalized models list
|
|
3811
3946
|
`save_journals`. A Mongoid document's own callbacks list in the same run
|
|
3812
3947
|
order, one in a class method where the document calls it. Neither tier
|
|
3813
|
-
evaluates `send`, `define_singleton_method` or a module included in
|
|
3814
|
-
|
|
3815
|
-
|
|
3948
|
+
evaluates `send`, `define_singleton_method` or a module included in `class
|
|
3949
|
+
<< self` for this, so the list can miss what they register or show the
|
|
3950
|
+
definition they replace. (#253)
|
|
3816
3951
|
- **A lambda or Proc given to `queue_as` reads as what ActiveJob does with
|
|
3817
3952
|
it.** ActiveJob never calls it; it names the queue after the Proc's text.
|
|
3818
3953
|
Both tiers now say so, with the source, where the booted tier printed the
|
|
@@ -3832,32 +3967,31 @@ Ten tools that each answered one section of the app's configuration leave
|
|
|
3832
3967
|
### Fixed
|
|
3833
3968
|
|
|
3834
3969
|
- **A concern's declarations name the module the include resolves to.**
|
|
3835
|
-
`rails_get_callbacks` on
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
- **A module declared in its outer module's file is read from there.**
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3970
|
+
`rails_get_callbacks` on a WorkPackage model lists `after_save
|
|
3971
|
+
:save_journals` from `Acts::Journalized::SaveHooks`, where it said
|
|
3972
|
+
`SaveHooks`.
|
|
3973
|
+
- **A module declared in its outer module's file is read from there.** A
|
|
3974
|
+
`Role::AssociationHelper` can live in role.rb, and its `included` hook adds
|
|
3975
|
+
`before_save :resolve_cross_account_role` to Enrollment and its subclasses,
|
|
3976
|
+
AccountUser and RoleOverride; the static tier called the module unread and
|
|
3977
|
+
missed the callback. `rails_get_callbacks`, `rails_get_model_details` and
|
|
3978
|
+
the controller filters and actions find it there and read that module, not
|
|
3979
|
+
the outer class.
|
|
3845
3980
|
- **`rails_get_callbacks` lists models of one callback count by name**, so a
|
|
3846
3981
|
model gaining a callback no longer moves the sections around it.
|
|
3847
|
-
- **A model macro written over `*args` reads each call's own arguments.**
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
`
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
under a condition the source does not decide".
|
|
3982
|
+
- **A model macro written over `*args` reads each call's own arguments.** The
|
|
3983
|
+
list binds to the call's positionals past the method's other parameters,
|
|
3984
|
+
less the options hash the body takes off the end (`args.extract_options!`),
|
|
3985
|
+
plus literals its leading statements push. A block over the list is read
|
|
3986
|
+
once per item, so `validates_locale :locale, :browser_locale, allow_nil:
|
|
3987
|
+
true` lists an inclusion validation on each field with its own `if:
|
|
3988
|
+
:<field>_changed?`, in place of the macro's own row and one on a computed
|
|
3989
|
+
`field`. An options hash, list or parameter the method changes in place
|
|
3990
|
+
(`options[:x] = 1`, `options[:x] ||= 1`, `reverse_merge!`, `delete`) is not
|
|
3991
|
+
read as the call's value. A declaration it decides, the method name included
|
|
3992
|
+
(`before_save name` after `name.strip!`), is held back and listed under "Not
|
|
3993
|
+
decided from the source (the method name or a condition)", the heading that
|
|
3994
|
+
was "Only under a condition the source does not decide".
|
|
3861
3995
|
- **A `load_defaults` written after `belongs_to_required_by_default = false`
|
|
3862
3996
|
turns the default back on** in the static tier, as Rails does, so the
|
|
3863
3997
|
implicit presence of a required `belongs_to` is listed. A literal assignment
|
|
@@ -3990,7 +4124,7 @@ Ten tools that each answered one section of the app's configuration leave
|
|
|
3990
4124
|
without `--line`, and prints the tier footer once.
|
|
3991
4125
|
- **`rails_validate` names a `belongs_to` whose key column the table lacks**
|
|
3992
4126
|
instead of calling that column unindexed, and says nothing about a table's
|
|
3993
|
-
indexes when its schema calls a helper the reader cannot interpret (
|
|
4127
|
+
indexes when its schema calls a helper the reader cannot interpret (such as
|
|
3994
4128
|
`t.replica_identity_index`), since that helper may add the index.
|
|
3995
4129
|
- **A multi-name `t.references :a, :b, :c` adds every column**, with its index
|
|
3996
4130
|
and foreign key, in the schema.rb reader and the migration replay. The replay
|
|
@@ -4070,11 +4204,10 @@ Ten tools that each answered one section of the app's configuration leave
|
|
|
4070
4204
|
|
|
4071
4205
|
A pass over the whole tree for code that did not need to exist, with one base
|
|
4072
4206
|
class for the introspectors and one for the serializers, and one reader where
|
|
4073
|
-
several files each kept a copy. Then four real-app QA passes in every tier
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
about 8,500 lines, most of it reading what the gem used to guess.
|
|
4207
|
+
several files each kept a copy. Then four real-app QA passes in every tier,
|
|
4208
|
+
against thirteen open-source and private Rails apps, and five review rounds
|
|
4209
|
+
found the wrong answers below. Their fixes grew `lib/` by about 8,500 lines,
|
|
4210
|
+
most of it reading what the gem used to guess.
|
|
4078
4211
|
|
|
4079
4212
|
### Added
|
|
4080
4213
|
|
|
@@ -4083,7 +4216,7 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4083
4216
|
one's path and the models the model scan found under it (unavailable, not
|
|
4084
4217
|
zero, when the model scan failed), and
|
|
4085
4218
|
in the compact files one Stack line, `- In-repo engines: 4 (catalog,
|
|
4086
|
-
|
|
4219
|
+
feed_provider, order_management, web)`, capped at five names.
|
|
4087
4220
|
|
|
4088
4221
|
### Changed
|
|
4089
4222
|
|
|
@@ -4172,11 +4305,11 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4172
4305
|
`; 29 in-repo engine locale dirs not read`.
|
|
4173
4306
|
- **The component catalog reads every `app/components`**, packs and in-repo
|
|
4174
4307
|
engines included, and its header counts what it could not place, as in `998
|
|
4175
|
-
components (862 ViewComponent, 0 Phlex, 136 of no known base class)` on
|
|
4176
|
-
|
|
4177
|
-
components inherit from (
|
|
4178
|
-
line and not counted; the generated files print the same line from the
|
|
4179
|
-
numbers. `component:` takes a name with or without its namespace or
|
|
4308
|
+
components (862 ViewComponent, 0 Phlex, 136 of no known base class)` on one
|
|
4309
|
+
app, which read 559 from its root tree alone, with a base that other
|
|
4310
|
+
components inherit from (another app's two `BaseComponent`s) named on a
|
|
4311
|
+
bases line and not counted; the generated files print the same line from the
|
|
4312
|
+
same numbers. `component:` takes a name with or without its namespace or
|
|
4180
4313
|
`Component` suffix, and a short name several components share lists them and
|
|
4181
4314
|
asks for the full one.
|
|
4182
4315
|
- **`rails_get_active_support` lists validator classes apart from concerns**,
|
|
@@ -4204,9 +4337,9 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4204
4337
|
beside the associations they came from, so the numbers reconcile with the
|
|
4205
4338
|
diagram.
|
|
4206
4339
|
- **An association whose name is computed is marked `(computed)` wherever it
|
|
4207
|
-
prints** (`` `owner_name` (computed) ``, and
|
|
4208
|
-
|
|
4209
|
-
`[INFERRED]` (six times in
|
|
4340
|
+
prints** (`` `owner_name` (computed) ``, and ``
|
|
4341
|
+
`:"#{name.underscore}_search_data"` (computed) ``), where it printed
|
|
4342
|
+
`[INFERRED]` (six times in one app's CLAUDE.md). No tool reads it as a
|
|
4210
4343
|
literal: `rails_dependency_graph` draws no node for it,
|
|
4211
4344
|
`rails_generate_test` writes no matcher for it, where it wrote a spec that
|
|
4212
4345
|
did not pass, and the related-model and N+1 checks name no model for it. A
|
|
@@ -4217,10 +4350,10 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4217
4350
|
connected first and then by name, through the same function `rails_onboard`
|
|
4218
4351
|
uses, and the heading says so. A subclass ranks by the associations it adds
|
|
4219
4352
|
to its parent, so one that only repeats them sinks and one with many of its
|
|
4220
|
-
own (
|
|
4221
|
-
subclasses, with no `User`, `Event` or `Scenario`, and
|
|
4222
|
-
|
|
4223
|
-
|
|
4353
|
+
own (such as a `User`) stays: one app's list was `Agent` and 14 of its
|
|
4354
|
+
subclasses, with no `User`, `Event` or `Scenario`, and another's was headed
|
|
4355
|
+
by `Chat::NullUser`. They listed the first 15 by name, and `rails_onboard`
|
|
4356
|
+
broke ties in file order.
|
|
4224
4357
|
- **The controller count beside a route count reads "routed controllers"** in
|
|
4225
4358
|
CLAUDE.md, AGENTS.md, `rails_onboard` and `rake ai:inspect`, since it counts
|
|
4226
4359
|
the controllers the routes name, not the controller files.
|
|
@@ -4244,14 +4377,14 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4244
4377
|
inferred. No two controllers share an identifier across code roots: an
|
|
4245
4378
|
in-repo plugin's `foo_controller.js` beside the app's reads `chat--foo`,
|
|
4246
4379
|
marked inferred. A controller the app registers from an installed package
|
|
4247
|
-
(
|
|
4248
|
-
|
|
4249
|
-
|
|
4380
|
+
(`reveal` from `@stimulus-components/reveal`) is listed as registered from
|
|
4381
|
+
that package, where `rails_get_stimulus` said none while `rails_get_view`
|
|
4382
|
+
showed it. One app's 161 read the names its code writes
|
|
4250
4383
|
(`admin--custom-fields`, not the path's `dynamic--admin--custom-fields`),
|
|
4251
4384
|
and the few nothing confirms are marked inferred, named by their directory's
|
|
4252
4385
|
rule when the app shows one (a loader importing the directory by a derived
|
|
4253
|
-
path, or confirmed neighbours that all drop its segment), so
|
|
4254
|
-
|
|
4386
|
+
path, or confirmed neighbours that all drop its segment), so its 8 read
|
|
4387
|
+
`generic-dialog-close`, not `dynamic--generic-dialog-close`. The empty
|
|
4255
4388
|
answer names the directories searched, and `rails_get_conventions` labels
|
|
4256
4389
|
the pattern `Stimulus controllers` with no directory after it.
|
|
4257
4390
|
- **The frontend framework is picked by config files and component counts**, a
|
|
@@ -4260,8 +4393,8 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4260
4393
|
also uses is named. An Angular app that also depends on React read as React,
|
|
4261
4394
|
and so did a Preact or Solid app that lists `react` for compatibility. Each
|
|
4262
4395
|
framework reports its own version, and a frontend root counts the components
|
|
4263
|
-
its framework writes (
|
|
4264
|
-
|
|
4396
|
+
its framework writes (one app's `frontend` has 305 `*.component.ts`; it read
|
|
4397
|
+
3).
|
|
4265
4398
|
- **`rails_get_test_info` names both frameworks** (`rspec, minitest`) when an
|
|
4266
4399
|
app runs both.
|
|
4267
4400
|
- **The conventions' Locales line names the locales each file declares** (a
|
|
@@ -4277,7 +4410,7 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4277
4410
|
partials share lists them**, where it answered with the first by path, and a
|
|
4278
4411
|
call to a helper the app defines (in `app/helpers` of any code root, or a
|
|
4279
4412
|
module those helpers include from any autoload root; a module's class
|
|
4280
|
-
methods are not helpers) is not one of a partial's locals:
|
|
4413
|
+
methods are not helpers) is not one of a partial's locals: one app's
|
|
4281
4414
|
`layouts/head` listed `canonical_link_tag` and three more.
|
|
4282
4415
|
- **A context run reads more of the app, for more time where there is more to
|
|
4283
4416
|
read.** Where a kind of code lives is resolved once per root, each file is
|
|
@@ -4289,13 +4422,13 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4289
4422
|
edit right after a parse on a coarse Linux clock is read again. The CLI
|
|
4290
4423
|
picks between a built-in tool and a custom one of the same name as the MCP
|
|
4291
4424
|
server does: the built-in, unless `skip_tools` names it. On the static tier,
|
|
4292
|
-
`context --format all` takes about the same CPU as 5.29.0 on
|
|
4293
|
-
against 18.1s
|
|
4294
|
-
|
|
4295
|
-
against 2.5s; booted 5.4s against 4.7s), where the `Services:` line
|
|
4296
|
-
all its services, and half again on
|
|
4297
|
-
30 in-repo modules are now read for models, Stimulus identifiers,
|
|
4298
|
-
mailers and model bases.
|
|
4425
|
+
`context --format all` takes about the same CPU as 5.29.0 on three of the
|
|
4426
|
+
apps tested (17.4s against 18.1s, 2.8s against 2.7s, and one more), a sixth
|
|
4427
|
+
more on a small app (1.1s against 1.0s), a quarter more on a private API app
|
|
4428
|
+
(3.1s against 2.5s; booted 5.4s against 4.7s), where the `Services:` line
|
|
4429
|
+
reads all its services, and half again on a large app (9.4s against 6.4s),
|
|
4430
|
+
whose 30 in-repo modules are now read for models, Stimulus identifiers,
|
|
4431
|
+
concerns, mailers and model bases.
|
|
4299
4432
|
- **The generated files' `Async:` line counts Sidekiq workers** beside jobs and
|
|
4300
4433
|
mailers, and `rails_get_job_pattern` names the directories it read under
|
|
4301
4434
|
every listing and also searches `app/sidekiq` for what enqueues a job. The
|
|
@@ -4303,7 +4436,7 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4303
4436
|
names and a count of the rest, where it globbed `app/jobs/*.rb`.
|
|
4304
4437
|
- **A worker entry says what a job entry says**: its line count, the
|
|
4305
4438
|
`retry_on` and `discard_on` it declares and what it calls. Where a base
|
|
4306
|
-
class owns `perform` and the worker defines `execute`, as
|
|
4439
|
+
class owns `perform` and the worker defines `execute`, as some apps' jobs
|
|
4307
4440
|
do, `execute` is read instead.
|
|
4308
4441
|
- **A class in a job directory that reaches neither ActiveJob nor Sidekiq but
|
|
4309
4442
|
defines `perform` is listed as `[unknown base]`**, as a Resque job is, and
|
|
@@ -4316,8 +4449,8 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4316
4449
|
ActiveJob's `perform_all_later` do (a private API app listed 3 of the 6
|
|
4317
4450
|
files that enqueue one of its workers); a call through the app's own
|
|
4318
4451
|
`enqueue`, `enqueue_in` or `enqueue_at` helper counts, the job named by a
|
|
4319
|
-
symbol, a string or the class (
|
|
4320
|
-
|
|
4452
|
+
symbol, a string or the class (`Jobs.enqueue(:process_post)` lists
|
|
4453
|
+
`app/models/post.rb`, where jobs enqueued that way had no callers); all of
|
|
4321
4454
|
`app/` and `lib/` is searched, rake tasks, `bin/` and `script/` programs and
|
|
4322
4455
|
all of `db/` included, and a list capped at twenty says how many more there
|
|
4323
4456
|
are; a job named relative to the caller's namespace (`SyncJob` inside
|
|
@@ -4332,28 +4465,28 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4332
4465
|
out by the constant it declares.
|
|
4333
4466
|
- **A job shows the queue and Sidekiq options it inherits from the app's own
|
|
4334
4467
|
base classes**, its own settings winning key by key, each inherited one
|
|
4335
|
-
named once with its base and its value when short.
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4468
|
+
named once with its base and its value when short. `Mail::*` workers showed
|
|
4469
|
+
no queue where `Mail::NotifierBaseWorker` sets `queue: low`, `Fasp::*`
|
|
4470
|
+
workers none where their base sets `fasp`, and `PublishingApi*Job`s none
|
|
4471
|
+
where theirs sets `publishing_api`.
|
|
4339
4472
|
- **A job queue computed by a block or an expression says so, with its
|
|
4340
4473
|
source**: `queue_as -> { ... }` read `dynamic` in the booted tier and showed
|
|
4341
4474
|
no queue without boot.
|
|
4342
4475
|
- **A class is a job base only when its own chain reaches ActiveJob or a
|
|
4343
|
-
Sidekiq mixin.**
|
|
4344
|
-
|
|
4476
|
+
Sidekiq mixin.** A `Meetings::PDF::Common::Styles::Base`, a PDF style class
|
|
4477
|
+
under `app/workers`, was listed as a job base.
|
|
4345
4478
|
- **A mailer kept in `app/services` is a mailer and not also a service**, and
|
|
4346
4479
|
a service not found is offered the constants the files declare
|
|
4347
4480
|
(`ActivityPub::...`, where it offered `Activitypub::...`).
|
|
4348
4481
|
- **A module other classes in the app include, extend or prepend is not a
|
|
4349
4482
|
service object**, unless it has an entry point of its own (a class method
|
|
4350
4483
|
other than the `included`, `extended` or `prepended` hook,
|
|
4351
|
-
`module_function`, `extend self`).
|
|
4352
|
-
|
|
4353
|
-
|
|
4484
|
+
`module_function`, `extend self`). An `AssetManager::ServiceHelper` was
|
|
4485
|
+
listed as a service, as were 11 mixins in a private API app and 11 in
|
|
4486
|
+
another app; the generated `Services:` line follows.
|
|
4354
4487
|
- **`rails_get_service_pattern` and `rails_get_helper_methods` find a service
|
|
4355
4488
|
or helper by the name the app writes when its path uses a registered
|
|
4356
|
-
acronym.**
|
|
4489
|
+
acronym.** One app's 20 `ActivityPub::...` services, in `activitypub/`,
|
|
4357
4490
|
answered not found and suggested `Activitypub::...`.
|
|
4358
4491
|
- **The generated files' `Services:` line reads the same scan
|
|
4359
4492
|
`rails_get_service_pattern` lists from**, so a service in a subdirectory, a
|
|
@@ -4363,12 +4496,12 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4363
4496
|
tier: the static tier dropped both with the conventions it cannot read.
|
|
4364
4497
|
- **A blank name given to a by-name lookup is answered as blank**, naming what
|
|
4365
4498
|
the tool looks up ("The concern name is blank"), in the helper, service,
|
|
4366
|
-
job, mailer, component and concern tools:
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4499
|
+
job, mailer, component and concern tools: `rails_get_helper_methods(helper:
|
|
4500
|
+
"")` crashed on a file named `helper.rb`. A string passed on the command
|
|
4501
|
+
line or in a direct call where a tool takes a list (`files`, `include`,
|
|
4502
|
+
`checks`) is a one-item list, or a list when comma-separated, where it
|
|
4503
|
+
failed with `undefined method 'any?'`; over MCP the schema asks for the list
|
|
4504
|
+
itself.
|
|
4372
4505
|
- **The generated Commands section and `rails_onboard`'s Getting Started name
|
|
4373
4506
|
only commands the app has**, from one decision: `bin/dev` where it exists,
|
|
4374
4507
|
otherwise `bin/rails server`, and `bin/rails db:migrate` where the app has
|
|
@@ -4381,35 +4514,35 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4381
4514
|
default read as `[]` where the booted tier printed `{}`, a booted expression
|
|
4382
4515
|
index reads as a key list, as the dumps give it (`rails_migration_advisor`
|
|
4383
4516
|
crashed on any table that has one), and schema hints print a structure.sql
|
|
4384
|
-
app's primary key as `id` (a composite one as `a, b`), where
|
|
4385
|
-
|
|
4517
|
+
app's primary key as `id` (a composite one as `a, b`), where one app's read
|
|
4518
|
+
``(pk: `["id"]`)``.
|
|
4386
4519
|
- **Static routes read the route files `config.paths["config/routes.rb"]`
|
|
4387
4520
|
registers**, in order (an assignment, `<<`, `concat`, `Rails.root.join`, a
|
|
4388
4521
|
literal `Dir[...]` glob, kept inside the app), and name a mounted app built
|
|
4389
|
-
by a call (`Flipper::UI.app`).
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4522
|
+
by a call (`Flipper::UI.app`). One app read 69 of its 340 app routes, and
|
|
4523
|
+
its Rswag, FeedProvider, Flipper UI and Sidekiq mounts were missing. A file
|
|
4524
|
+
pulled in by `draw` is read inside the scope its `draw` sits in, and again
|
|
4525
|
+
under each scope that draws it: one app's `draw :api` inside `namespace
|
|
4526
|
+
:api` put `/articles/search` under the web `articles` controller without
|
|
4527
|
+
`/api`, and its v0 routes were dropped. `match ... via:` is one route
|
|
4528
|
+
answering each verb it names (`GET|POST`, `ANY` for `via: :all`, a `via:`
|
|
4529
|
+
from an enclosing scope too), where static routes dropped it. Routes take
|
|
4530
|
+
the controller from `scope(controller:)` or their own `controller:` (one app
|
|
4531
|
+
read 297 made-up controllers such as `accounts/:account_id`), routes drawn
|
|
4532
|
+
through an app class (`ApiRouteSet::V1.draw(self)`) take the path and name
|
|
4533
|
+
prefixes it returns (its 971 `/api/v1` routes), a path under an optional
|
|
4534
|
+
scope is written as Rails normalizes it, and a route name another file
|
|
4535
|
+
already took is left off, as Rails does; a singular `resource` names its
|
|
4536
|
+
show route and a root route takes its `as:` as Rails does, an engine lists
|
|
4537
|
+
what it could not expand and every path it is mounted at, a controller
|
|
4405
4538
|
filter lists engine rows like the app's own, and every route count says how
|
|
4406
4539
|
many more routes sit in engine tables.
|
|
4407
4540
|
- **The auth section's CORS flag is the api section's decision** (a
|
|
4408
4541
|
commented-out `cors.rb` is not configured in either), so `.ai-context.json`
|
|
4409
|
-
no longer says
|
|
4542
|
+
no longer says an app has no CORS beside its `rack-cors.rb`.
|
|
4410
4543
|
- **The locale note counts every in-repo gem, engine or plugin with
|
|
4411
|
-
`config/locales`**, those without `app/` too (
|
|
4412
|
-
|
|
4544
|
+
`config/locales`**, those without `app/` too (one app: 46 directories, where
|
|
4545
|
+
it said 28).
|
|
4413
4546
|
- **The booted database and connection-pool sections list replicas**
|
|
4414
4547
|
(`configs_for` with `include_hidden: true`), as the static tier does.
|
|
4415
4548
|
- **`rails_security_scan` and `doctor` read the app's `Gemfile.lock` before
|
|
@@ -4423,28 +4556,28 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4423
4556
|
holds every ancestor and mixin, and otherwise names the unread gem or
|
|
4424
4557
|
ancestor and says the action is unverified; a namespaced controller is
|
|
4425
4558
|
checked at all, and a route to a method ending in `?` or `!` finds it
|
|
4426
|
-
(
|
|
4559
|
+
(`requests#merged?` was flagged).
|
|
4427
4560
|
- **An engine's root, an initializer's source and an autoload directory in the
|
|
4428
4561
|
context are app-relative, or the gem and version**, the gem's own
|
|
4429
4562
|
initializers and autoload directories are left out, and is left out rather
|
|
4430
4563
|
than written as an absolute path from the generating machine.
|
|
4431
|
-
- **Routes an app draws into an engine (`
|
|
4564
|
+
- **Routes an app draws into an engine (`Shop::Core::Engine.routes.draw`) are
|
|
4432
4565
|
listed with that engine**, under the path the app mounts it at and named
|
|
4433
|
-
through its route proxy (`
|
|
4566
|
+
through its route proxy (`shop.admin_orders_path`, after the mount's `as:`
|
|
4434
4567
|
or the engine's `engine_name`), and are not in the app's route count on
|
|
4435
4568
|
either tier. Booted, the listing is each mounted engine's whole table, read
|
|
4436
4569
|
under its mount the way `bin/rails routes` prints it; statically, it is what
|
|
4437
4570
|
the app's route files draw into the engine. Controller actions, route-helper
|
|
4438
4571
|
validation and generated tests read these routes too.
|
|
4439
|
-
`rails_get_routes(controller: "
|
|
4440
|
-
such routes, a short name such as `orders` lists them beside the app's
|
|
4441
|
-
matches, and the listing names the 219 it draws into
|
|
4442
|
-
PUT merged as in the app count.
|
|
4572
|
+
`rails_get_routes(controller: "shop/admin/orders")` answers with one app's
|
|
4573
|
+
15 such routes, a short name such as `orders` lists them beside the app's
|
|
4574
|
+
own matches, and the listing names the 219 it draws into the engine apart,
|
|
4575
|
+
PATCH and PUT merged as in the app count.
|
|
4443
4576
|
- **A mounted app is listed once per app and path, with its path** (the
|
|
4444
4577
|
compact `Mounted:` line names each app once, with its path count, before its
|
|
4445
4578
|
five-name cap), when the mount sits in a `scope path: nil` or in both
|
|
4446
|
-
branches of an `if`:
|
|
4447
|
-
|
|
4579
|
+
branches of an `if`: one app's "Mounted Apps (4)" listed `Sidekiq::Web` and
|
|
4580
|
+
`Logster::Web` twice each with no path.
|
|
4448
4581
|
- **`rails_get_env` and the context file's `env` section read ENV names from
|
|
4449
4582
|
one scan**, and a default is redacted by the source rule, so an empty one,
|
|
4450
4583
|
an address, a URL or a hostname prints as written where it read
|
|
@@ -4452,8 +4585,8 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4452
4585
|
other `config/**/*.yml`, skipping `<%#` comments. Config YAML on
|
|
4453
4586
|
`sensitive_patterns` is read for the names in its ERB tags only, never a
|
|
4454
4587
|
value or a default; credentials and keys are still never opened.
|
|
4455
|
-
`rails_get_env` gains 19 names on a private API app, 13 on
|
|
4456
|
-
|
|
4588
|
+
`rails_get_env` gains 19 names on a private API app, 13 on another and 9 on
|
|
4589
|
+
a third (its `DATABASE_HOST`, `DATABASE_PORT`, `DATABASE_SOCKET`).
|
|
4457
4590
|
- **A connected app with no tables yet is described as connected**, where the
|
|
4458
4591
|
schema note said "no DB connection, no schema.rb".
|
|
4459
4592
|
- **`rails_security_scan` reports brakeman's error message**, where it quoted
|
|
@@ -4471,24 +4604,24 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4471
4604
|
`include` adds its concern's `before_action`s where the line stands, and
|
|
4472
4605
|
each prints the concern it came from (`_(from Localized via
|
|
4473
4606
|
ApplicationController)_`), in `rails_get_controllers`,
|
|
4474
|
-
`rails_analyze_feature` and the generated "Global before_actions".
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4607
|
+
`rails_analyze_feature` and the generated "Global before_actions". An
|
|
4608
|
+
`AboutController` listed none of `WebAppControllerConcern`'s four filters or
|
|
4609
|
+
the `set_locale` its parent's `Localized` wraps every action in, and
|
|
4610
|
+
`FollowerAccountsController` had no `set_account`. An included module whose
|
|
4611
|
+
file is not in the app is named as not read.
|
|
4479
4612
|
- **An included module is found in any `app/*` directory**, as Rails autoloads
|
|
4480
|
-
it, for a model as for a controller:
|
|
4481
|
-
|
|
4482
|
-
|
|
4613
|
+
it, for a model as for a controller: a `Budget::Investment` carries the
|
|
4614
|
+
`after_save` its `Budget::Reclassification` declares, a `WorkPackage`, which
|
|
4615
|
+
read 0 validations and 17 unread concerns, reads its
|
|
4483
4616
|
`app/models/work_package/validations.rb`, and a model that includes a helper
|
|
4484
4617
|
from `app/helpers` no longer reports it as not read.
|
|
4485
4618
|
- **A partial index prints its WHERE clause.** The static tier carries the
|
|
4486
4619
|
`where:` of a schema.rb index, the WHERE of a structure.sql `CREATE INDEX`
|
|
4487
4620
|
and the `where:` a replayed migration passes, and `rails_get_schema` and
|
|
4488
|
-
`.claude/rules` print it:
|
|
4489
|
-
(published IS TRUE)]` and
|
|
4490
|
-
|
|
4491
|
-
|
|
4621
|
+
`.claude/rules` print it: a `canonical_url` index reads `[unique where
|
|
4622
|
+
(published IS TRUE)]` and a `unique_index_categories_on_slug` ends `where
|
|
4623
|
+
((slug)::text <> ''::text)`, where both read as unique over the whole table;
|
|
4624
|
+
a column two partial unique indexes cover shows each condition.
|
|
4492
4625
|
- **"Missing FK Indexes" reports a column only when an association or a
|
|
4493
4626
|
foreign key uses it.** Any `*_id` column whose type matched some table's
|
|
4494
4627
|
primary key counted, so an app with one string key reported every external
|
|
@@ -4501,14 +4634,14 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4501
4634
|
it as the parent key. The associations are the ones the models section
|
|
4502
4635
|
lists, so a model's concerns and inherited bases count, and so do models
|
|
4503
4636
|
that subclass `ActiveRecord::Base`. Against 5.29.0: a private API app lists
|
|
4504
|
-
6 where it listed 111, all six real;
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4637
|
+
6 where it listed 111, all six real; three other apps 16 where they listed
|
|
4638
|
+
20, 15 where 13 and 43 where 36; a fourth 166 where it listed 2, because its
|
|
4639
|
+
models subclass `ActiveRecord::Base` and none were read before.
|
|
4640
|
+
`rails_performance_check` and the "Performance: N issues detected" line in
|
|
4641
|
+
the generated files follow (135 to 30 on a private API app). A
|
|
4642
|
+
`has_and_belongs_to_many` record in the models section now carries a
|
|
4643
|
+
declared `join_table` and `association_foreign_key` on both tiers, so a
|
|
4644
|
+
custom join table is read by name booted as well as static.
|
|
4512
4645
|
- **The gem's own warnings never reach the JSON-RPC stream.** While the stdio
|
|
4513
4646
|
transport owns stdout, including while live reload starts, a warning goes to
|
|
4514
4647
|
stderr rather than `Rails.logger`, which an app may have pointed at stdout.
|
|
@@ -4543,41 +4676,42 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4543
4676
|
- **`config/database.yml` is read as YAML in the static tier.** It was scanned
|
|
4544
4677
|
line by line, and the scan disagreed with the file format on every shape
|
|
4545
4678
|
that is not the generated one: an entry sharing its adapter through `<<:
|
|
4546
|
-
*default` came back with only its name, a database called `replica` (as
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
where it read `PostgreSQL or SQLite`; `postgis` reads as
|
|
4562
|
-
when nothing decides, the reason names only the files the
|
|
4563
|
-
nothing to decide between them, the answer names the
|
|
4564
|
-
SQLite`, and in `rails_get_schema` `MySQL or SQLite,
|
|
4565
|
-
say which`). A file that is not valid YAML reports no
|
|
4566
|
-
misreading. A boot that fails part way now falls back
|
|
4567
|
-
database and replica lists, where it reported none. By
|
|
4568
|
-
that mixes plain keys with a nested `secondary:` hash
|
|
4569
|
-
Rails reads it, so that nested entry is no longer
|
|
4679
|
+
*default` came back with only its name, a database called `replica` (as some
|
|
4680
|
+
apps name a read replica) was read as a config key of the one before it, so
|
|
4681
|
+
the primary was labelled the replica and the app read as single-database,
|
|
4682
|
+
and an env given only a `url:` listed no database at all. The file is now
|
|
4683
|
+
parsed by `YAML` with aliases on, after its ERB tags are replaced without
|
|
4684
|
+
being run, and the databases come out of the env hash by the same rule Rails
|
|
4685
|
+
applies: every value a Hash means one database per key, anything else is a
|
|
4686
|
+
single primary. An adapter the file computes in ERB is reported as the
|
|
4687
|
+
literal the ERB falls back to, labelled so (`MySQL by database.yml
|
|
4688
|
+
default`), or as unknown when there is none; it is never picked from the
|
|
4689
|
+
Gemfile when the app bundles more than one adapter gem. Without a committed
|
|
4690
|
+
`config/database.yml`, the adapter is read from the schema first, a
|
|
4691
|
+
structure.sql dialect and then column types only one database has (`jsonb`,
|
|
4692
|
+
arrays, `hstore`; `mediumtext`), then from the app's `database.yml.*`
|
|
4693
|
+
example files, then the Gemfile, so an app with no committed file reads
|
|
4694
|
+
PostgreSQL where it read `PostgreSQL or SQLite`; `postgis` reads as
|
|
4695
|
+
PostgreSQL, and when nothing decides, the reason names only the files the
|
|
4696
|
+
app has. With nothing to decide between them, the answer names the
|
|
4697
|
+
candidates (`MySQL or SQLite`, and in `rails_get_schema` `MySQL or SQLite,
|
|
4698
|
+
database.yml does not say which`). A file that is not valid YAML reports no
|
|
4699
|
+
databases instead of a misreading. A boot that fails part way now falls back
|
|
4700
|
+
to the file for the database and replica lists, where it reported none. By
|
|
4701
|
+
the same rule, an env that mixes plain keys with a nested `secondary:` hash
|
|
4702
|
+
is one database, as Rails reads it, so that nested entry is no longer
|
|
4703
|
+
reported as a replica.
|
|
4570
4704
|
- **An association reads the options of a `with_options` block around it.**
|
|
4571
4705
|
The static tier ignored the block, so `rails_get_model_details`,
|
|
4572
4706
|
`rails_dependency_graph` and `.ai-context.json` named classes the app does
|
|
4573
|
-
not have (
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4707
|
+
not have (an `EmailDomainBlock` parent and child read as `Parent` and
|
|
4708
|
+
`Child`, `Appeal`'s reviewers as `ApprovedByAccount`), and `rails_validate`
|
|
4709
|
+
reported a `has_many` inside `with_options dependent: :destroy` as missing
|
|
4710
|
+
`:dependent`; one `Account` model drew 42 such warnings. It follows
|
|
4711
|
+
ActiveSupport: a block without a parameter passes its options to the
|
|
4712
|
+
associations inside it, a block with one (`do |assoc| assoc.has_many ...
|
|
4713
|
+
end`) passes them only to the calls on that parameter, and the association's
|
|
4714
|
+
own options still win over the block's.
|
|
4581
4715
|
- **`rails_validate` compares an escaped symbol by its real characters.** A
|
|
4582
4716
|
`validates`, `permit` or callback symbol written with an escape no longer
|
|
4583
4717
|
reads as a missing column.
|
|
@@ -4612,9 +4746,9 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4612
4746
|
payload's job and worker records carry one `retries` list in place of
|
|
4613
4747
|
`retry_on` and `discard_on`.
|
|
4614
4748
|
- **`config/sidekiq.yml` is read as YAML**, so a path named in a comment and
|
|
4615
|
-
an environment key are no longer listed as queues (
|
|
4616
|
-
|
|
4617
|
-
|
|
4749
|
+
an environment key are no longer listed as queues (one app read `default,
|
|
4750
|
+
mailers, config`), and a weighted queue (`[critical, 8]`) is named by its
|
|
4751
|
+
queue.
|
|
4618
4752
|
- **A page past the end of a list says only that.** `rails_get_i18n` and
|
|
4619
4753
|
`rails_get_mailers` asked for such a page said the app had no locale files
|
|
4620
4754
|
or no mailers, beside the count that proved otherwise.
|
|
@@ -4628,18 +4762,17 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4628
4762
|
'production'?"); `RAILS_ENV` stays settable through the variable, or with
|
|
4629
4763
|
`--environment` before the command name.
|
|
4630
4764
|
- **The route total agrees across surfaces.** `.ai-context.json` and the
|
|
4631
|
-
generated files counted one route twice where two controllers answer on
|
|
4632
|
-
|
|
4633
|
-
|
|
4765
|
+
generated files counted one route twice where two controllers answer on the
|
|
4766
|
+
same path and action (two API versions), so they read 769 beside
|
|
4767
|
+
`rails_get_routes`' 765 plus 3 framework routes.
|
|
4634
4768
|
- **A share short of the whole never prints as 100%.** Translation coverage,
|
|
4635
4769
|
the doctor readiness score, connection-pool use and the cache hit rate are
|
|
4636
4770
|
floored, so a locale missing one key reads `99.9%` where it read `100.0%`
|
|
4637
4771
|
beside the line naming the missing key.
|
|
4638
|
-
- **An authorization library is named only when the app bundles it.** A
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
`Auth: Pundit`.
|
|
4772
|
+
- **An authorization library is named only when the app bundles it.** A bare
|
|
4773
|
+
`app/policies` directory reads "policies in app/policies" with the policy
|
|
4774
|
+
classes listed, and an `Ability` class without cancancan is named as that
|
|
4775
|
+
file, where an app that bundles neither read `Auth: Pundit`.
|
|
4643
4776
|
- **A Brakeman finding from `rails_validate` prints under the file it
|
|
4644
4777
|
belongs to**, where every finding landed under the last file listed.
|
|
4645
4778
|
- **The ActiveStorage stack line counts models and attachments apart** (`14
|
|
@@ -4684,15 +4817,15 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4684
4817
|
than 5.0; a proc default reads as its source, as schema.rb and structure.sql
|
|
4685
4818
|
give it. `rails_get_schema` prints where the answer came from at every
|
|
4686
4819
|
detail level, including how many names were read from files and how many
|
|
4687
|
-
`create_table` calls stayed unnamed.
|
|
4820
|
+
`create_table` calls stayed unnamed. One app goes from 37 tables and 39
|
|
4688
4821
|
indexes to 208 and 556, and `rails_get_schema(table: "work_packages")`
|
|
4689
4822
|
answers where it said the table did not exist.
|
|
4690
4823
|
- **A route target with a leading slash is absolute**, as Rails reads it:
|
|
4691
|
-
|
|
4692
|
-
|
|
4824
|
+
`controller: "/admin/settings/..."` inside admin namespaces read
|
|
4825
|
+
`admin/settings/admin/settings/...`.
|
|
4693
4826
|
- **`app/concerns` is read as a concerns directory**, in the root tree, packs
|
|
4694
|
-
and in-repo engines, as Rails autoloads it.
|
|
4695
|
-
there
|
|
4827
|
+
and in-repo engines, as Rails autoloads it. An app that keeps its model
|
|
4828
|
+
concerns there read 1 concern where it has 28, and `rails_get_model_details`
|
|
4696
4829
|
called 8 of `Agent`'s concerns unread; `rails_get_concern(type: "other")`
|
|
4697
4830
|
lists them. `type:` takes every type the listing prints, in either spelling
|
|
4698
4831
|
(`service`, `LibStatic`), and only types that hold a concern are listed, and
|
|
@@ -4700,49 +4833,48 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4700
4833
|
such a value and listed every concern. A module a concern declares inside
|
|
4701
4834
|
itself and mixes in (`DryRunnable::Wrapper`) is looked up from the enclosing
|
|
4702
4835
|
namespace outward, and a mixin in an autoload root the app declares
|
|
4703
|
-
(`autoload_lib`, `autoload_paths`) is read like any concern, so
|
|
4836
|
+
(`autoload_lib`, `autoload_paths`) is read like any concern, so a
|
|
4704
4837
|
`RateLimiter::OnCreateRecord` adds its callbacks to `Post` and `Topic`. A
|
|
4705
4838
|
macro, callback or mixin inside a method of such a module applies only to
|
|
4706
4839
|
the classes that call that method, in their own body or through a concern's
|
|
4707
|
-
`included do` block, as at runtime: every
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
`
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
`WorkPackage::InexistentWorkPackage` lacked the `journals` its parent has).
|
|
4840
|
+
`included do` block, as at runtime: every model that includes the module
|
|
4841
|
+
holding `acts_as_watchable` would otherwise show `watchers` and `favorites`.
|
|
4842
|
+
The macros are read with the call's literal arguments (a parameter the
|
|
4843
|
+
method reassigns or a block shadows keeps its own value), taking the `if`,
|
|
4844
|
+
`unless` or `case` branch they decide (a private API app's own macro called
|
|
4845
|
+
with `:picture, has_one: true` gives `has_one :picture`, not a `name` pair);
|
|
4846
|
+
a declaration only a condition the literals cannot decide holds is not
|
|
4847
|
+
listed or counted, and is named with its condition under "Only under a
|
|
4848
|
+
condition the source does not decide" (a `WorkPackage` showed a `has_many
|
|
4849
|
+
custom_comments` it does not have). A called method the reader cannot read
|
|
4850
|
+
costs only itself, named as unread; a literal hash argument binds as
|
|
4851
|
+
keywords (`validates_translation :summary, presence: true`) and is read
|
|
4852
|
+
through `merge`, `slice`, `except` and `reject`, so translated validations
|
|
4853
|
+
read as `presence` and `length` under their condition, an unused `options =
|
|
4854
|
+
{}` no longer swallows the next declaration, a parameter reassigned in a
|
|
4855
|
+
block keeps its own value, and what a macro method declares inside a block
|
|
4856
|
+
it evaluates on another receiver (`translation_class.instance_eval {
|
|
4857
|
+
validates ... }`) is named under "Declared on another class" with the
|
|
4858
|
+
condition it runs under, and not counted as the model's (a `Proposal` read
|
|
4859
|
+
20 validation lines, 10 of them its translation class's). What a mixin hook
|
|
4860
|
+
declares (`def self.included(base)` and the like, in `class_eval`, as
|
|
4861
|
+
`base.has_many`, `base.scope` or `base.delegate`, or through a helper it
|
|
4862
|
+
calls) applies to every includer, each hook only for its own way of mixing
|
|
4863
|
+
in (`included` on include, `prepended` on prepend, `extended` on extend). A
|
|
4864
|
+
module the app mixes into `ActiveRecord::Base` or `ApplicationRecord` from
|
|
4865
|
+
an initializer, `lib`, `lib_static` or a plugin's `init.rb` (a reopened
|
|
4866
|
+
class, `send(:include)`, an `on_load` hook, a required file) is every
|
|
4867
|
+
model's mixin, read again on each run so `watch` and the MCP server see one
|
|
4868
|
+
added later, a model's own `extend` of an app module is read the same way
|
|
4869
|
+
(`resolves_root_account` gives `belongs_to :root_account`), a module the
|
|
4870
|
+
model's own file declares is not reported unread (a `WorkPackage` gains
|
|
4871
|
+
`journals`, `attachments` and `custom_values`), a mixin in a path gem inside
|
|
4872
|
+
the repo is read like the app's own code (`gems/broadcast_policy` and
|
|
4873
|
+
`gems/workflow`), and one from a gem installed outside the repo is named as
|
|
4874
|
+
unread. One rule answers which classes mix in a module, for concerns,
|
|
4875
|
+
services and join tables alike, and a subclass inherits what its parent's
|
|
4876
|
+
concerns call into (`WorkPackage::InexistentWorkPackage` lacked the
|
|
4877
|
+
`journals` its parent has).
|
|
4746
4878
|
- **Index hints follow how an index is used**, their clauses parted by
|
|
4747
4879
|
semicolons so a clause listing several columns reads as one, each clause
|
|
4748
4880
|
printed once. A column is `[indexed]` only when it leads an index and
|
|
@@ -4756,14 +4888,13 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4756
4888
|
warns of a duplicate index only when the name `add_index` would generate
|
|
4757
4889
|
exists.
|
|
4758
4890
|
- **The `structure.sql` reader keeps an expression index key whole** and the
|
|
4759
|
-
keys after it, and no longer reads `DESC`, `NULLS LAST` or an operator
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
and now has 583.
|
|
4891
|
+
keys after it, and no longer reads `DESC`, `NULLS LAST` or an operator class
|
|
4892
|
+
as a column: `(COALESCE(parent_category_id, '-1'::integer), name)` read as
|
|
4893
|
+
four columns with `name` lost. It also reads each column's default, the way
|
|
4894
|
+
the `schema.rb` reader reports one (a literal as its value, a cast string as
|
|
4895
|
+
the string, json and arrays as Rails dumps them, an expression as `-> {
|
|
4896
|
+
"CURRENT_TIMESTAMP" }`, a serial key's `nextval` as none): one app's schema
|
|
4897
|
+
had no default hints at all and now has 583.
|
|
4767
4898
|
- **Both search backends return the same lines.** The Ruby fallback matched
|
|
4768
4899
|
case-insensitively, searched only a list of extensions and read
|
|
4769
4900
|
`excluded_paths` as path prefixes, so on a private API app one model name
|
|
@@ -4777,9 +4908,9 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4777
4908
|
matches ignore patterns case-sensitively, leaves symlinks alone, walks the
|
|
4778
4909
|
tree once without entering an ignored directory, and decides a binary file
|
|
4779
4910
|
from its first 64 KiB: 14.5 s to 2.5 s on a private API app and 27.5 s to
|
|
4780
|
-
8.6 s on
|
|
4781
|
-
file that is not UTF-8 is reported as a match, where it came back as
|
|
4782
|
-
fake `error:0: invalid byte sequence` row.
|
|
4911
|
+
8.6 s on one large app, with the same lines ripgrep returns. A ripgrep match
|
|
4912
|
+
in a file that is not UTF-8 is reported as a match, where it came back as
|
|
4913
|
+
one fake `error:0: invalid byte sequence` row.
|
|
4783
4914
|
- **`rails_search_code` never answers "No matches" while matches exist.** A
|
|
4784
4915
|
`limit` smaller than one match and its context lines held only context, so
|
|
4785
4916
|
the page came back empty; it now starts at the next match and says so.
|
|
@@ -4807,67 +4938,66 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4807
4938
|
test files, their helpers and `spec/factories`, not from whether a `spec/`
|
|
4808
4939
|
directory exists. With no tests anywhere, the answer says so and names the
|
|
4809
4940
|
framework the bundle holds (`no tests yet (rspec-rails in the bundle)`),
|
|
4810
|
-
where it said the app ran rspec or minitest.
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4941
|
+
where it said the app ran rspec or minitest. Two minitest apps whose `spec/`
|
|
4942
|
+
holds only JavaScript tests read as RSpec, and `rails_generate_test` wrote
|
|
4943
|
+
them an RSpec spec. `doctor` reads the same answer (`minitest test suite
|
|
4944
|
+
found`, or `No test suite found` where it said `No test directory found`),
|
|
4945
|
+
and an app that runs both is told to run `bundle exec rspec` where it was
|
|
4946
|
+
told `rails test`.
|
|
4816
4947
|
- **Tests are found and written where the app keeps them.**
|
|
4817
4948
|
`rails_get_test_info(model:)` and `(controller:)` look in the directories
|
|
4818
|
-
the app's tests live in, so
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
`
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
test class and data style (factories or fixtures) most of the app's
|
|
4949
|
+
the app's tests live in, so `test/functional` and `test/unit/app/models`
|
|
4950
|
+
tests are found, where the tool said "No test file found" for files its own
|
|
4951
|
+
no-argument answer counted, and `rails_generate_test` writes into the
|
|
4952
|
+
directory most of the app's model or controller tests share, under the
|
|
4953
|
+
file-name suffix most of them use, falling back to the convention when there
|
|
4954
|
+
are none, and names the subject's existing test rather than writing a second
|
|
4955
|
+
one. A controller's lookup also lists the system, feature, request and
|
|
4956
|
+
integration tests named for it or reaching its routes, each labelled by kind
|
|
4957
|
+
(an `AboutController` said "No test file found" beside
|
|
4958
|
+
`spec/system/about_spec.rb`). A model's tests are looked for only where the
|
|
4959
|
+
app keeps model tests: a private API app's `rails_get_test_info(model:
|
|
4960
|
+
"User")` answered with a serializer spec sharing the basename, where its
|
|
4961
|
+
model spec is `spec/models/user_model_spec.rb`. A generated controller test
|
|
4962
|
+
asserts what each action answers, `create`, `update` and `destroy` included,
|
|
4963
|
+
`respond_with` read by format and verb as the responder answers and an early
|
|
4964
|
+
bare `return` counted as an answer (an API `create` rendering `status:
|
|
4965
|
+
:created` or a `head :no_content` destroy no longer gets the scaffold's
|
|
4966
|
+
redirect), read from its body and its `respond_to` block for the format the
|
|
4967
|
+
test sends, the branch valid params take where a save splits it (a redirect
|
|
4968
|
+
as `:redirect` with its target when the test can name it, `head` as its
|
|
4969
|
+
status, a JSON or plain render with its media type, a conditional or
|
|
4970
|
+
unreadable action only as not a server error, with a comment saying why),
|
|
4971
|
+
where it asserted `:success` for a redirecting `verify_email`. A controller
|
|
4972
|
+
under a gem's controller (Devise) keeps its routed actions, unverified;
|
|
4973
|
+
RSpec controller specs include `Devise::Test::ControllerHelpers` (Devise
|
|
4974
|
+
setup in `spec/support` counts), a test of a Devise controller sets
|
|
4975
|
+
`devise.mapping` from Devise's route names, a `Post` model gets
|
|
4976
|
+
`let(:post_record)` so the `post` request method is not shadowed, a destroy
|
|
4977
|
+
test builds its record before reading its id, a redirect target is asserted
|
|
4978
|
+
only when it names a route, and `rails_generate_test` looks for an existing
|
|
4979
|
+
test where `rails_get_test_info` does. A namespaced model is built from the
|
|
4980
|
+
controller's namespace outward (a namespaced `LineItemsController` got a
|
|
4981
|
+
skip), an `on:` validation is checked in its context and a conditioned one
|
|
4982
|
+
is skipped with its condition named, and a glob segment is passed. It covers
|
|
4983
|
+
only the actions the controller has (its own and inherited public actions,
|
|
4984
|
+
template-backed ones, and names a `define_method` or a concern may build),
|
|
4985
|
+
naming each routed action it leaves out, so a `resources` route no longer
|
|
4986
|
+
gets a `show` test for a controller with no `show`. It passes every required
|
|
4987
|
+
path segment and builds its record under its route parent (contacts under
|
|
4988
|
+
organisations raised `UrlGenerationError`), leaves optional segments out,
|
|
4989
|
+
fills a custom segment from the column of the same name on the record or its
|
|
4990
|
+
parent (`graph/file/:uid/:id` gets `uid: @csvfile.uid`), and skips with the
|
|
4991
|
+
segment named when it cannot fill one; one written among an app's controller
|
|
4992
|
+
specs is a `type: :controller` spec. A generated minitest controller test
|
|
4993
|
+
uses the test class and data style (factories or fixtures) most of the app's
|
|
4864
4994
|
controller tests use, each test's superclass read through Prism and followed
|
|
4865
4995
|
through an app base class in the test tree (`::ActionController::TestCase`
|
|
4866
4996
|
counts), and a model's fixtures are found under its pluralized class name or
|
|
4867
4997
|
its `set_fixture_class` mapping (read from the test and spec helpers and
|
|
4868
4998
|
support files), a namespaced model's under the names Rails gives them
|
|
4869
|
-
(`admin_users(:one)`, where it wrote `admin/users(:one)`):
|
|
4870
|
-
|
|
4999
|
+
(`admin_users(:one)`, where it wrote `admin/users(:one)`): a `Node`, whose
|
|
5000
|
+
table is `node`, got a TODO beside a `nodes.yml`.
|
|
4871
5001
|
- **`rails_get_test_info` counts the examples a test file runs, and lists
|
|
4872
5002
|
exactly those** (`it`, `specify`, `scenario`, `test`, `def test_*`,
|
|
4873
5003
|
one-liners too, never a `describe` or `context` line: a private API app's
|
|
@@ -4883,20 +5013,19 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4883
5013
|
one, and the gems payload says which source answered
|
|
4884
5014
|
(`declared_ruby_version_source`).
|
|
4885
5015
|
- **The Rails version is read from `railties`** when an app bundles it without
|
|
4886
|
-
the `rails` gem
|
|
5016
|
+
the `rails` gem.
|
|
4887
5017
|
- **`--no-boot` reads the `config/database.yml` section for `RAILS_ENV` or
|
|
4888
5018
|
`RACK_ENV`**, where it always read `development`.
|
|
4889
|
-
- **Code in an in-repo engine, plugin or module is read like `app/`**:
|
|
4890
|
-
|
|
5019
|
+
- **Code in an in-repo engine, plugin or module is read like `app/`**: models,
|
|
5020
|
+
controllers, views, concerns, services, jobs, mailers, helpers and
|
|
4891
5021
|
serializers under `plugins/*`, `modules/*` and `gems/plugins/*`. A view
|
|
4892
5022
|
there reaches `rails_get_view`, `rails_get_partial_interface`,
|
|
4893
|
-
`rails_get_turbo_map` and the view lookups, and can be read by path
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
the Gemfile.
|
|
5023
|
+
`rails_get_turbo_map` and the view lookups, and can be read by path (one
|
|
5024
|
+
app: 318 to 492 templates). A directory counts when it has its own `app/`
|
|
5025
|
+
with a Rails directory in it and a gemspec, a `plugin.rb` or an `engine.rb`
|
|
5026
|
+
says Ruby loads it. One app goes from 223 to 359 models and another from 169
|
|
5027
|
+
to 274, and `rails_get_schema` stops calling a plugin's table model-less and
|
|
5028
|
+
pointing at the Gemfile.
|
|
4900
5029
|
- **A model reads the table prefix its namespace gives it**, from a `def
|
|
4901
5030
|
self.table_name_prefix` on the namespace or an engine's `isolate_namespace`,
|
|
4902
5031
|
as Rails' `full_table_name_prefix` does; an explicit `self.table_name =`
|
|
@@ -4905,13 +5034,12 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4905
5034
|
`config.active_record`'s), and a derived name takes
|
|
4906
5035
|
`config.active_record.table_name_prefix` and `_suffix` when nothing nearer
|
|
4907
5036
|
sets one. A class nested in a concrete model takes that model's singular
|
|
4908
|
-
table as a prefix, as Rails' `compute_table_name` does:
|
|
4909
|
-
|
|
4910
|
-
`
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
`rails_get_schema` called their tables model-less.
|
|
5037
|
+
table as a prefix, as Rails' `compute_table_name` does: a `Project::Phase`
|
|
5038
|
+
reads `project_phases` where it read `phases`, and its `Principal`, `User`,
|
|
5039
|
+
`Group` and four more read `users` where they read `principals`. 51 of one
|
|
5040
|
+
app's models (its engine models and their calculators) read `orders` where
|
|
5041
|
+
the table is `shop_orders`, and a plugin's `RssPolling::RssFeed` read
|
|
5042
|
+
`rss_feeds`, so `rails_get_schema` called their tables model-less.
|
|
4915
5043
|
- **`rails_get_schema` names the gem that owns a table with no model file**,
|
|
4916
5044
|
one line per gem the lockfile or Gemfile has (good_job, doorkeeper,
|
|
4917
5045
|
friendly_id, closure_tree, paper_trail, acts-as-taggable-on, pghero, the
|
|
@@ -4925,33 +5053,31 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4925
5053
|
suffix reads as the table it names, so a join table is not called model-less
|
|
4926
5054
|
either.
|
|
4927
5055
|
- **A migration's literal `execute("DROP TABLE ...")` drops the table from the
|
|
4928
|
-
replayed schema**, MySQL's backtick-quoted names included.
|
|
5056
|
+
replayed schema**, MySQL's backtick-quoted names included. An app's
|
|
4929
5057
|
`meeting_contents` and `meeting_content_journals` were listed, and four
|
|
4930
5058
|
missing-index suggestions came with them.
|
|
4931
5059
|
- **Jobs are read from `app/jobs`, `app/workers` and `app/sidekiq` alike.** An
|
|
4932
5060
|
app with its workers in the Sidekiq 7 generator directory, or its ActiveJob
|
|
4933
|
-
classes in `app/workers`, answered "No jobs found" (
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
for `Jobs::AnonymizeUser`.
|
|
5061
|
+
classes in `app/workers`, answered "No jobs found" (one app has 31, another
|
|
5062
|
+
103 with its modules). A class is a worker when a Sidekiq mixin is anywhere
|
|
5063
|
+
up its chain and a job when its chain reaches ActiveJob, wherever it lives,
|
|
5064
|
+
so a Sidekiq job under `app/jobs` shows the queue its `sidekiq_options`
|
|
5065
|
+
names rather than the default. An abstract base, one another job inherits
|
|
5066
|
+
from and whose name says it is a base (`Jobs::Base`, `ApplicationWorker`),
|
|
5067
|
+
is no longer counted as a job, in either tier. A job is named by the
|
|
5068
|
+
constant its file declares when that names its own namespace: one app's read
|
|
5069
|
+
`Regular::AnonymizeUser` for `Jobs::AnonymizeUser`.
|
|
4943
5070
|
- **A mailer is listed wherever it lives and whatever it declares.** One
|
|
4944
|
-
outside `app/mailers` is found by its parent chain anywhere under `app/`
|
|
4945
|
-
|
|
5071
|
+
outside `app/mailers` is found by its parent chain anywhere under `app/` (an
|
|
5072
|
+
app that keeps `Mailer < ActionMailer::Base` in `app/models` and got "_No
|
|
4946
5073
|
mailers found._"), a chain is followed through the app's autoload roots to
|
|
4947
5074
|
`ActionMailer::Base` or a gem mailer, a class under `app/mailers` whose
|
|
4948
|
-
chain reaches no mailer is not one (
|
|
5075
|
+
chain reaches no mailer is not one (one app listed twelve header builders
|
|
4949
5076
|
with `Actions: set_headers`), and one with no action of its own is listed
|
|
4950
5077
|
with the parent it takes its actions from and any class methods it has
|
|
4951
|
-
(
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
interface.
|
|
5078
|
+
(`MultiNotifications`, a `DeviseMailer` and an `AppDeviseMailer`). A mailer
|
|
5079
|
+
a gem defines is not listed as the app's, and an ActionMailer setting such
|
|
5080
|
+
as `mailer_name` is not a mailer's interface.
|
|
4955
5081
|
- **A by-name lookup answers the name it was given, or says it has no such
|
|
4956
5082
|
thing** and suggests the closest, by Ruby's own spell checker over full
|
|
4957
5083
|
names and last segments, where it offered whatever shared the first three
|
|
@@ -4976,30 +5102,29 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
4976
5102
|
gems put into every model, a booted mailer lists only the actions a mailer
|
|
4977
5103
|
class defines, conventions claim model concerns only from a concerns file
|
|
4978
5104
|
that is a module, and `rails_onboard` names Mongoid for a Mongoid app, where
|
|
4979
|
-
|
|
5105
|
+
a Mongoid app read "on unknown".
|
|
4980
5106
|
- **Neither tier lists a mailer's or a controller's action callbacks,
|
|
4981
5107
|
predicates or bang methods as actions**, nor a controller method that needs
|
|
4982
5108
|
an argument, which Rails could not dispatch. The booted tier listed what
|
|
4983
|
-
`action_methods` returns; a private API app's `BaseController` listed its
|
|
4984
|
-
`before_action` target and a predicate. A controller lists the actions
|
|
4985
|
-
inherits from every app base, whatever the base is called and however its
|
|
4986
|
-
name is qualified (
|
|
4987
|
-
`
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
actions an included app concern offers (Whitehall's
|
|
5109
|
+
`action_methods` returns; a private API app's `BaseController` listed its
|
|
5110
|
+
own `before_action` target and a predicate. A controller lists the actions
|
|
5111
|
+
it inherits from every app base, whatever the base is called and however its
|
|
5112
|
+
name is qualified (`CustomFields::Hierarchy::ItemsBaseController`, written
|
|
5113
|
+
inside `Admin::Settings`), in its actions and its filter chain; an inherited
|
|
5114
|
+
public method that no route names for the controller is a shared helper and
|
|
5115
|
+
not an action (paper_trail's `info_for_paper_trail` read as an action of
|
|
5116
|
+
every controller in a private API app), and an unrouted base controller
|
|
5117
|
+
lists only the actions its subclasses are routed for, a controller under
|
|
5118
|
+
`app/controllers` no route names lists only what it defines itself, a
|
|
5119
|
+
controller lists the public actions an included app concern offers (an
|
|
4995
5120
|
`Admin::ContactTranslationsController` lacked `create`, `update` and
|
|
4996
5121
|
`destroy`), and the booted tier drops a method that needs an argument and
|
|
4997
5122
|
settles inherited actions by the routes, as the static tier does; a routed
|
|
4998
|
-
action with a template and no method is an action (
|
|
5123
|
+
action with a template and no method is an action (an
|
|
4999
5124
|
`Admin::OrderCyclesController` lacked `incoming`, `outgoing` and
|
|
5000
5125
|
`checkout_options`); and a file under `app/controllers` not named
|
|
5001
|
-
`*_controller.rb` is a controller only when it subclasses one, so
|
|
5002
|
-
|
|
5126
|
+
`*_controller.rb` is a controller only when it subclasses one, so Grape API
|
|
5127
|
+
classes stay out.
|
|
5003
5128
|
- **A Stimulus controller outside `app/javascript/controllers` is found**:
|
|
5004
5129
|
under `app/webpacker`, `app/frontend`, `frontend`, `client`, component
|
|
5005
5130
|
sidecars in `app/components`, and the same homes in packs and in-repo
|
|
@@ -5007,15 +5132,15 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
5007
5132
|
Outside the rails-new home a file counts only when it imports from
|
|
5008
5133
|
`@hotwired/stimulus` or `stimulus`, or extends a class that does (an app
|
|
5009
5134
|
file reached relatively or through the app's alias table, or an installed
|
|
5010
|
-
Stimulus package), so an AngularJS `*.controller.js`,
|
|
5011
|
-
`activity-base.controller.ts` or a React `*_controller.tsx` does not.
|
|
5012
|
-
read 1 of
|
|
5135
|
+
Stimulus package), so an AngularJS `*.controller.js`, an Angular
|
|
5136
|
+
`activity-base.controller.ts` or a React `*_controller.tsx` does not. Three
|
|
5137
|
+
apps read 1 of 23, 0 of 73 and 0 of 161.
|
|
5013
5138
|
- **Editing a Stimulus controller outside `app/javascript/controllers`, or a
|
|
5014
5139
|
job in `app/workers` or `app/sidekiq`, invalidates the cached context**, so
|
|
5015
5140
|
tools stop answering from stale data.
|
|
5016
|
-
- **The app's architecture lists `stimulus` and `hotwire` wherever it keeps
|
|
5017
|
-
Stimulus controllers.**
|
|
5018
|
-
A React component named `*_controller.tsx` still does not count.
|
|
5141
|
+
- **The app's architecture lists `stimulus` and `hotwire` wherever it keeps
|
|
5142
|
+
its Stimulus controllers.** Two apps that keep them elsewhere read as having
|
|
5143
|
+
none. A React component named `*_controller.tsx` still does not count.
|
|
5019
5144
|
- **`rails_get_stimulus` counts a view as a use only when its markup names the
|
|
5020
5145
|
controller** in a `data-controller` attribute, a target, an action or a
|
|
5021
5146
|
`content_controller`-style helper. An i18n key or a word in prose with the
|
|
@@ -5031,8 +5156,8 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
5031
5156
|
- **A markup comment is not markup.** A HAML `-#` comment, with the lines
|
|
5032
5157
|
indented under it, and an ERB `<%# %>` comment are no longer read for
|
|
5033
5158
|
partials, instance variables, Stimulus identifiers, slots, fields or helper
|
|
5034
|
-
calls:
|
|
5035
|
-
|
|
5159
|
+
calls: a `create_linked_variant.turbo_stream.haml` listed a partial named
|
|
5160
|
+
`the`, from `-# Pre-render the form`.
|
|
5036
5161
|
- **A `render` call's partial is read with or without parentheses**, wherever
|
|
5037
5162
|
its keyword sits: `render(partial: 'x')`, `render 'x'`, `render(template:
|
|
5038
5163
|
'x')`, `render collection: @items, partial: 'x'`, the `:partial =>` form and
|
|
@@ -5042,14 +5167,14 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
5042
5167
|
partial, and an escaped quote in an earlier argument no longer hides it; a
|
|
5043
5168
|
turbo frame id or stream name holding a comma inside a string is read whole,
|
|
5044
5169
|
and a Stimulus `static values` default holding a brace, a paren or a regex
|
|
5045
|
-
literal is read whole, in source order.
|
|
5046
|
-
|
|
5170
|
+
literal is read whole, in source order. One app's views gained 51 partial
|
|
5171
|
+
references and another's 23.
|
|
5047
5172
|
- **A layout's yields are read in ERB, HAML and Slim**: the unnamed one as
|
|
5048
5173
|
`(main)`, `yield(:x)`, `(yield :x).presence` and `content_for?(:x)`, read
|
|
5049
5174
|
only where Ruby runs (ERB tags, HAML code lines and attribute hashes, Slim
|
|
5050
5175
|
code and attribute values), and a `content_for` that sets content, with a
|
|
5051
|
-
block or a value, parenthesised or not, is not a yield.
|
|
5052
|
-
reported none.
|
|
5176
|
+
block or a value, parenthesised or not, is not a yield. One app's six
|
|
5177
|
+
layouts reported none.
|
|
5053
5178
|
- **A Stimulus action descriptor is read with its options, event filters and
|
|
5054
5179
|
neighbours** (`click->x#open:prevent`, `keydown.enter->`, `resize@window->`,
|
|
5055
5180
|
several descriptors in one `data-action`), read only from an action value (a
|
|
@@ -5057,14 +5182,14 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
5057
5182
|
Ruby variable such as `confirm_actions:` or `hiddenFieldAction:`), so an
|
|
5058
5183
|
apostrophe in page text no longer hides the actions after it, and prose, a
|
|
5059
5184
|
Ruby comment or a Rails route list (`%w[posts#create]`) confirms nothing,
|
|
5060
|
-
where
|
|
5185
|
+
where one app's `bulk-form` and `modal` uses were missed.
|
|
5061
5186
|
- **`url_for(controller: "x")` is not a Stimulus controller** in view answers.
|
|
5062
5187
|
- **`data-turbo` counts read the HAML and Ruby-hash spellings**
|
|
5063
5188
|
(`"data-turbo": "false"`, `'data-turbo' => 'false'`), not only
|
|
5064
5189
|
`data-turbo="false"`.
|
|
5065
5190
|
- **A model whose `inclusion: { in: [...] }` list mixes strings with `nil` no
|
|
5066
5191
|
longer fails** `rails_get_model_details` and `rails_context` with
|
|
5067
|
-
`ArgumentError: comparison of String with nil failed`.
|
|
5192
|
+
`ArgumentError: comparison of String with nil failed`. One app's `Edition`
|
|
5068
5193
|
and its 13 STI subclasses failed.
|
|
5069
5194
|
- **A validation or callback inside `with_options` carries the block's
|
|
5070
5195
|
options**, so a validation scoped `on: :publish` no longer reads as always
|
|
@@ -5078,8 +5203,8 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
5078
5203
|
as the file wrote it.
|
|
5079
5204
|
- **A Mongoid app counts `embeds_many` and `embeds_one` as associations
|
|
5080
5205
|
everywhere, and lists only documents as models.** `rails_dependency_graph`
|
|
5081
|
-
draws their edges, and
|
|
5082
|
-
plain Ruby class under `app/models` (
|
|
5206
|
+
draws their edges, and one app's `App` read "1 association" beside 5 edges;
|
|
5207
|
+
a plain Ruby class under `app/models` (such as `ErrorReport` or `Issue`) is
|
|
5083
5208
|
not a model.
|
|
5084
5209
|
- **Both tiers list every declared validation the same way, with its
|
|
5085
5210
|
conditions**: `validates_with`, `validates_date`, `validates_translation`
|
|
@@ -5100,17 +5225,17 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
5100
5225
|
validation whose attribute is computed, as in a loop, says so, and a custom
|
|
5101
5226
|
`validate :method` prints its `on:`, `if:` or `unless:` condition in both
|
|
5102
5227
|
tiers. A private API app's booted `Document` dropped its `if:` conditions
|
|
5103
|
-
and printed its `validates_with` line as `document on`, the static tier
|
|
5104
|
-
|
|
5228
|
+
and printed its `validates_with` line as `document on`, the static tier left
|
|
5229
|
+
`validates_with` out (one app's `User` read 0 validations), and a
|
|
5105
5230
|
hand-written presence rule on a `belongs_to` name was labelled implicit.
|
|
5106
5231
|
- **An inheritance column set to `nil`, `false` or `:_type_disabled` reads as
|
|
5107
5232
|
STI off**, where the conventions claimed STI. A validator option set to
|
|
5108
5233
|
`false` (`presence: false`) is no validation, in both tiers; a class's own
|
|
5109
5234
|
`belongs_to_required_by_default` beats the app's and passes to its
|
|
5110
|
-
subclasses (
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5235
|
+
subclasses (a `Subscription` showed 7 implicit presence lines); a computed
|
|
5236
|
+
enum prints its source marked computed, and a frozen literal one its values;
|
|
5237
|
+
and one `after_commit` declared for several events is one callback showing
|
|
5238
|
+
its events.
|
|
5114
5239
|
- **`rails_validate` judges only the `has_many` associations the model's file
|
|
5115
5240
|
declares**, and treats encrypted attributes (`has_encrypted`, `encrypts`,
|
|
5116
5241
|
`attr_encrypted`) and store keys as attributes with no column of their own,
|
|
@@ -5124,63 +5249,62 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
5124
5249
|
two associations of one kind to one class each get an arrow labelled by
|
|
5125
5250
|
name; and a through association that names no association of its model, or
|
|
5126
5251
|
whose name is computed at run time, draws no node and is named in a note
|
|
5127
|
-
saying which:
|
|
5128
|
-
`
|
|
5129
|
-
`
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
out when a model was passed.
|
|
5252
|
+
saying which: a `Budget` pointed at a top-level `Investment`, `Ballot`,
|
|
5253
|
+
`Group` and `Heading`, and a `Shop::Order` at `Payment`, `Shipment` and
|
|
5254
|
+
`Adjustment`. It draws a through edge per join model, where an `Account`
|
|
5255
|
+
lost its through `Follow`, `Block` and `Mute` edges behind the first one; a
|
|
5256
|
+
through association with `source_type:` points at the class `source_type`
|
|
5257
|
+
names; and the "pass model:" hint is left out when a model was passed.
|
|
5134
5258
|
- **A block callback a concern declares is kept beside the model's own block
|
|
5135
|
-
callback of the same kind**:
|
|
5136
|
-
|
|
5259
|
+
callback of the same kind**: a `Topic` lost the `after_create do` of
|
|
5260
|
+
`RateLimiter::OnCreateRecord`.
|
|
5137
5261
|
- **A static route honours the action its `action:` option names** (`get
|
|
5138
5262
|
:delete, action: :deletion_dialog` read as `delete`).
|
|
5139
5263
|
- **A component is typed by following its superclass chain through the app's
|
|
5140
5264
|
own base classes**, so one two or more steps from `ViewComponent::Base` or a
|
|
5141
|
-
Phlex base is no longer "unknown", and a sidecar file that reopens its
|
|
5142
|
-
namespace is named for the class the file is named for. A component
|
|
5143
|
-
in a module is listed by its full constant (`Admin::RowComponent`,
|
|
5144
|
-
three different classes all read `RowComponent`), which renames most
|
|
5145
|
-
|
|
5265
|
+
Phlex base is no longer "unknown", and a sidecar file that reopens its
|
|
5266
|
+
parent namespace is named for the class the file is named for. A component
|
|
5267
|
+
nested in a module is listed by its full constant (`Admin::RowComponent`,
|
|
5268
|
+
where three different classes all read `RowComponent`), which renames most
|
|
5269
|
+
of a large app's catalog.
|
|
5146
5270
|
- **A concern that nests its own `ActiveModel::Validator` is a concern**, not
|
|
5147
5271
|
a validator, and every surface that lists concerns or helpers names each one
|
|
5148
|
-
by the constant its file declares, in-repo plugins included
|
|
5149
|
-
`
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
`
|
|
5153
|
-
`
|
|
5272
|
+
by the constant its file declares, in-repo plugins included
|
|
5273
|
+
(`Subscriptions::Group` read `Group`, and `rails_get_helper_methods(helper:
|
|
5274
|
+
"ChatIntegration::Helper")` answered not found): `rails_get_active_support`
|
|
5275
|
+
read `Featurable` for `Edition::Featurable`, and an inflected namespace
|
|
5276
|
+
(`SDG::TagList` in `concerns/sdg/tag_list.rb`) is no longer spelled
|
|
5277
|
+
`Sdg::TagList`.
|
|
5154
5278
|
- **A module that extends `ActiveSupport::Concern` is a concern wherever the
|
|
5155
5279
|
app autoloads it from**, and the default `excluded_concerns` patterns each
|
|
5156
5280
|
match a whole namespace, so an app module named only like a framework's
|
|
5157
|
-
(
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5281
|
+
(`ActiveRecordLikeInterface`) is listed, not hidden; listed with its path
|
|
5282
|
+
under a type naming its root (`Service Concerns`), in `rails_get_concern`
|
|
5283
|
+
and `rails_get_active_support` alike: one app read 82 concerns and has 329,
|
|
5284
|
+
another 9 and 26, a private API app none and 2.
|
|
5161
5285
|
- **A concern's includes and methods are its own body's**, not those of
|
|
5162
|
-
classes nested in its file:
|
|
5163
|
-
|
|
5286
|
+
classes nested in its file: a `LiquidDroppable` read `Includes: Enumerable`
|
|
5287
|
+
and its nested `Drop` classes' `to_s`, `each` and `as_json`, and
|
|
5164
5288
|
`Agents::ImapFolderAgent` was credited with `Scrubbed`. A concern whose
|
|
5165
5289
|
methods are all private reads `0 public methods (N private)` and lists them,
|
|
5166
5290
|
where it read `0 methods`.
|
|
5167
5291
|
- **A concern's "Included By" lists the classes whose `include` resolves to
|
|
5168
5292
|
it** from their own namespace, each once, named by its constant, searching
|
|
5169
|
-
every autoload root and naming them when it finds none:
|
|
5293
|
+
every autoload root and naming them when it finds none: an
|
|
5170
5294
|
`Admin::ExportControllerConcern` listed 9 includers, 7 of them including
|
|
5171
|
-
`Settings::ExportControllerConcern`, and
|
|
5295
|
+
`Settings::ExportControllerConcern`, and another app listed
|
|
5172
5296
|
`SubscriptionsController` twice.
|
|
5173
5297
|
- **Component previews are read from the directories the config sets**
|
|
5174
5298
|
(`view_component.previews.paths`, `preview_paths`, `preview_path`) as well
|
|
5175
5299
|
as the defaults, and a preview links to its component by path or by the one
|
|
5176
|
-
component it renders:
|
|
5177
|
-
|
|
5300
|
+
component it renders: one app read no component with a preview, and 39 have
|
|
5301
|
+
one.
|
|
5178
5302
|
- **A boot error no longer repeats in full under every answer.** The footer
|
|
5179
5303
|
and each `[UNAVAILABLE]` note keep its first 140 characters, and the whole
|
|
5180
|
-
error stays in the startup banner and `doctor`. On
|
|
5181
|
-
|
|
5304
|
+
error stays in the startup banner and `doctor`. On one app it was 61% of the
|
|
5305
|
+
output.
|
|
5182
5306
|
- **`rails_get_view` does not count a partial under `app/views/layouts` as a
|
|
5183
|
-
layout.**
|
|
5307
|
+
layout.** One app read 19 layouts where it has 4.
|
|
5184
5308
|
- **An abstract base is not counted as a service, a job or a mailer.** A class
|
|
5185
5309
|
other classes inherit from and whose name says it is a base
|
|
5186
5310
|
(`ApplicationService`, `BaseService`, `Jobs::Base`, `ApplicationMailer`)
|
|
@@ -5192,14 +5316,13 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
5192
5316
|
declaration: a job base its queue, options, retry policy, mixins, throttle
|
|
5193
5317
|
and callbacks, a mailer base its layout, helpers, defaults, callbacks and
|
|
5194
5318
|
mixins as the file writes them, and either one the methods it defines and
|
|
5195
|
-
the classes that inherit it.
|
|
5196
|
-
as a service, and
|
|
5197
|
-
read as emails.
|
|
5319
|
+
the classes that inherit it. One app's generated files listed `BaseService`
|
|
5320
|
+
as a service, and two apps' `ApplicationMailer` helpers read as emails.
|
|
5198
5321
|
- **A class file is named by the constant it declares.** A model, job or
|
|
5199
5322
|
service whose file declares a namespaced constant at a path that does not
|
|
5200
5323
|
spell it is listed under that constant, where it was dropped or named from
|
|
5201
|
-
the path:
|
|
5202
|
-
|
|
5324
|
+
the path: a plugin's `GithubPlugin::GithubCommit` and six other plugin
|
|
5325
|
+
models were missing, and `Jobs::ChatAddTypeField` read
|
|
5203
5326
|
`Onceoff::AddTypeField`.
|
|
5204
5327
|
- **A bare superclass resolves from the enclosing namespace outward**, as
|
|
5205
5328
|
Ruby resolves it, so `module Bim::Bcf; class NonExistentComment < Comment`
|
|
@@ -5235,7 +5358,7 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
5235
5358
|
`graphql_name` is concrete, and a base is one others inherit from, or one
|
|
5236
5359
|
named like a base that subclasses graphql-ruby and adds nothing to the
|
|
5237
5360
|
schema. The enums, inputs, scalars and connections that subclass
|
|
5238
|
-
graphql-ruby directly still count (
|
|
5361
|
+
graphql-ruby directly still count (one app: 176 types, 102 mutations). An
|
|
5239
5362
|
app with no `queries/` directory is pointed at its query root file rather
|
|
5240
5363
|
than told "0 queries".
|
|
5241
5364
|
- **Middleware changes are read wherever the app makes them**: through
|
|
@@ -5247,17 +5370,17 @@ about 8,500 lines, most of it reading what the gem used to guess.
|
|
|
5247
5370
|
and `config/environments/*.rb` as well as the initializers. A class the app
|
|
5248
5371
|
owns is listed with its file, under the name its file declares, wherever the
|
|
5249
5372
|
app autoloads it from (`lib/middleware`, `app/lib/middlewares`) or wherever
|
|
5250
|
-
its config or initializers declare it with a `call` method (
|
|
5251
|
-
`TelemetryLoggingMiddleware`,
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5373
|
+
its config or initializers declare it with a `call` method (a
|
|
5374
|
+
`TelemetryLoggingMiddleware`, an `App::Cors`); `config.exceptions_app` is
|
|
5375
|
+
named apart from the stack; one it does not own is listed under "Stack
|
|
5376
|
+
changes from the app's config" as inserted, moved, swapped in or removed,
|
|
5377
|
+
each with the file that does it. One app read no custom middleware and 1
|
|
5378
|
+
insertion; it has 16 classes and 27 stack changes (19 inserted, 2 swapped
|
|
5379
|
+
in, 1 moved, 5 removed).
|
|
5257
5380
|
- **The autoload roots include the ones `config/application.rb` adds**
|
|
5258
5381
|
(`autoload_paths`, `eager_load_paths`, `autoload_lib`, `config.paths.add`),
|
|
5259
5382
|
when they are written as literal paths inside the app, so a class there is
|
|
5260
|
-
found as the app's own.
|
|
5383
|
+
found as the app's own. An app can keep middleware in `lib_static`.
|
|
5261
5384
|
|
|
5262
5385
|
### Removed
|
|
5263
5386
|
|
|
@@ -5851,12 +5974,12 @@ filing.
|
|
|
5851
5974
|
|
|
5852
5975
|
### Fixed
|
|
5853
5976
|
|
|
5854
|
-
Defects found by a ninth QA round of v5.25.0 against
|
|
5855
|
-
to #181), the sibling defects behind them, and what nine review
|
|
5856
|
-
repeated from-scratch verification found in the fixes themselves.
|
|
5857
|
-
entries below are older than the twenty-two reports; the reports
|
|
5858
|
-
looking started. One entry is a consumer-visible key rename and
|
|
5859
|
-
Changed.
|
|
5977
|
+
Defects found by a ninth QA round of v5.25.0 against a large open-source app
|
|
5978
|
+
(issues #160 to #181), the sibling defects behind them, and what nine review
|
|
5979
|
+
rounds and repeated from-scratch verification found in the fixes themselves.
|
|
5980
|
+
Most of the entries below are older than the twenty-two reports; the reports
|
|
5981
|
+
are where the looking started. One entry is a consumer-visible key rename and
|
|
5982
|
+
is under Changed.
|
|
5860
5983
|
|
|
5861
5984
|
- **`excluded_association_names` was ignored everywhere except the booted
|
|
5862
5985
|
ActiveRecord path.** The key had one call site, inside the reflection-only
|
|
@@ -5893,8 +6016,8 @@ Changed.
|
|
|
5893
6016
|
`after_rollback`.** `after_create_commit :x` and `after_commit :y, on:
|
|
5894
6017
|
:create` run at the same point, and the gem keeps each declaration as the
|
|
5895
6018
|
file wrote it, but the synthesized key was missing from the execution order
|
|
5896
|
-
so it sorted to the end.
|
|
5897
|
-
|
|
6019
|
+
so it sorted to the end. A `Status` model printed the second half of three
|
|
6020
|
+
events after the rollback callbacks.
|
|
5898
6021
|
- **Callbacks were named after the wrong thing, and `around` callbacks
|
|
5899
6022
|
disappeared.** The concern section re-parsed the file with a line regex
|
|
5900
6023
|
whose colon was optional, so `after_create do` printed as `after_create :do`
|
|
@@ -5914,7 +6037,7 @@ Changed.
|
|
|
5914
6037
|
section. It walks the source with the same listener the model and callback
|
|
5915
6038
|
tools use, and renders each declaration through the one helper they share.
|
|
5916
6039
|
- **The static tier answered `0 assoc` for a model whose associations all live
|
|
5917
|
-
in concerns.** It walked the model file alone, so
|
|
6040
|
+
in concerns.** It walked the model file alone, so one `Account` model
|
|
5918
6041
|
reported no associations where 68 are declared across the 21 concerns it
|
|
5919
6042
|
includes, and nothing marked the gap. Both tiers now walk the included
|
|
5920
6043
|
concern files with the same listeners and merge what they declare, tagged
|
|
@@ -5961,7 +6084,7 @@ Changed.
|
|
|
5961
6084
|
empty string.** The static tier kept the key on presence alone, so
|
|
5962
6085
|
`generate_test` wrote `it { is_expected.to have_many(:replies).dependent(:)
|
|
5963
6086
|
}` into the file the tool tells you to paste, and that line does not parse;
|
|
5964
|
-
the model details printed a blank `dependent:` for the same row.
|
|
6087
|
+
the model details printed a blank `dependent:` for the same row. One app
|
|
5965
6088
|
declares it nineteen times. A nil value is dropped now, the way the booted
|
|
5966
6089
|
tier already dropped it, and `optional: false` and `polymorphic: false` keep
|
|
5967
6090
|
their keys.
|
|
@@ -6015,7 +6138,7 @@ Changed.
|
|
|
6015
6138
|
- **A second `validates` on the same attribute and kind was dropped.**
|
|
6016
6139
|
`rails_get_model_details` collapsed validations on kind and attribute alone,
|
|
6017
6140
|
so a model that validates one attribute twice under different conditions
|
|
6018
|
-
lost the second rule;
|
|
6141
|
+
lost the second rule; one `Account` model lost its local-username length
|
|
6019
6142
|
limit and its `uri` exclusion. The options are part of the key now, and only
|
|
6020
6143
|
a byte-identical repeat is collapsed.
|
|
6021
6144
|
- **`rails_get_model_details` printed `validates` as the kind of a
|
|
@@ -6033,7 +6156,7 @@ Changed.
|
|
|
6033
6156
|
to a class named nowhere, and a `[VERIFIED]` block for a model that does not
|
|
6034
6157
|
exist. Two owners of the same association name also collapsed onto one
|
|
6035
6158
|
entry. A class whose name is not the constant it lives at is no longer a
|
|
6036
|
-
model, so
|
|
6159
|
+
model, so one app's model count drops from 117 to 114.
|
|
6037
6160
|
- **A gem's model was written into `.ai-context.json` with an app path that
|
|
6038
6161
|
does not exist.** Doorkeeper's three models were recorded at
|
|
6039
6162
|
`app/models/doorkeeper/access_grant.rb` and friends, the invented path the
|
|
@@ -6052,14 +6175,14 @@ Changed.
|
|
|
6052
6175
|
off the same file.
|
|
6053
6176
|
- **The static tier derived every table from the file name alone.** An
|
|
6054
6177
|
explicit `self.table_name =`, a `table_name_prefix` declared by the
|
|
6055
|
-
enclosing module and an STI child's parent table were all ignored, so
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6178
|
+
enclosing module and an STI child's parent table were all ignored, so an
|
|
6179
|
+
`Admin::ActionLog` answered `action_logs` and rendered no columns, and
|
|
6180
|
+
`admin_action_logs` had no model in the schema listing. `TableName` reads
|
|
6181
|
+
those declarations out of source and the model walk composes them in Rails'
|
|
6182
|
+
own order. The readers that turned a model name into a table go through the
|
|
6183
|
+
same module now, so `rails_get_schema(table: "Admin::ActionLog")` finds the
|
|
6184
|
+
table and `rails_migration_advisor` stops rejecting `admin/action_logs` as
|
|
6185
|
+
an invalid name.
|
|
6063
6186
|
- **`remove_column` advice could name an STI child's file.** The
|
|
6064
6187
|
`ignored_columns` step took the first model recorded against the table, and
|
|
6065
6188
|
every class in an STI family records the same one, so `AdminUser` could win
|
|
@@ -6091,23 +6214,22 @@ Changed.
|
|
|
6091
6214
|
rather than guessed.
|
|
6092
6215
|
- **A counter_cache suggestion named a model that does not exist.** The row
|
|
6093
6216
|
derived the far side of a `has_many` from the association name, so `has_many
|
|
6094
|
-
:remarks, class_name: "Comment"` sent the reader to a `Remark`, and
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6217
|
+
:remarks, class_name: "Comment"` sent the reader to a `Remark`, and a `Poll`
|
|
6218
|
+
named `Vote` and `Voter` for `PollVote`. It reads the association's own
|
|
6219
|
+
`class_name` now, drops the row when no model in the app answers it, skips a
|
|
6220
|
+
`has_many ... through:` because there is no `belongs_to` on the far side to
|
|
6221
|
+
carry the counter, and names a polymorphic inverse by the association's
|
|
6222
|
+
`:as` option.
|
|
6100
6223
|
- **Two n+1 findings on one association printed as one line twice.** The
|
|
6101
6224
|
controller and the action appeared only at full detail, so the default
|
|
6102
6225
|
listing showed identical rows and the section count did not reconcile with
|
|
6103
6226
|
what a reader could see. Every row carries its call site now.
|
|
6104
|
-
- **"Orphaned tables" named tables that have a model.** On
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
declares the table.
|
|
6227
|
+
- **"Orphaned tables" named tables that have a model.** On one app the warning
|
|
6228
|
+
listed seven tables of which one was real: four belong to Doorkeeper and
|
|
6229
|
+
PgHero, whose models live in the gem, and two are `has_and_belongs_to_many`
|
|
6230
|
+
join tables Rails builds no model for by design. Join tables are recognised
|
|
6231
|
+
from the association records and left out, and the line claims only what it
|
|
6232
|
+
can prove, that no model file in this app declares the table.
|
|
6111
6233
|
- **Routing concerns dropped their routes, and `with_options` and `module:`
|
|
6112
6234
|
named controllers that do not exist.** A `concern :x do ... end` body was
|
|
6113
6235
|
stored nowhere, so the controllers declared only inside it had no static
|
|
@@ -6115,17 +6237,17 @@ Changed.
|
|
|
6115
6237
|
`concerns:` site with the scope as it stands there, the way Rails re-runs it
|
|
6116
6238
|
on the mapper. `with_options` is an OptionMerger, not a routing method, so
|
|
6117
6239
|
it became a frame of default options that every call inside merges under its
|
|
6118
|
-
own - which is what stops six
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
`
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6240
|
+
own - which is what stops six account routes being filed under a controller
|
|
6241
|
+
named `@:username`, and stops `with_options only: [:index]` expanding a
|
|
6242
|
+
resource into all eight RESTful rows. Those defaults reach a `namespace` or
|
|
6243
|
+
`scope` inside the block too, where a `module:` or `as:` from them replaces
|
|
6244
|
+
the block's own name, as `defaults.merge!(options)` does in the mapper.
|
|
6245
|
+
`module:` on a resource moves the controller and never the path, so
|
|
6246
|
+
`admin/distributions` is `admin/announcements/distributions` and
|
|
6125
6247
|
`admin/terms_of_service/distributions` again. `as:` and `param:` on a
|
|
6126
6248
|
resource are read too, so the new rows carry the helper name and the member
|
|
6127
6249
|
segment the app actually has (`/users/:account_username`, not
|
|
6128
|
-
`/users/:account_id`). On
|
|
6250
|
+
`/users/:account_id`). On one app the static total falls, 658 routes to 657
|
|
6129
6251
|
and 22 undisclosed dynamic constructs to 21: 69 real rows arrive and 76
|
|
6130
6252
|
fabricated ones go.
|
|
6131
6253
|
- **A collection route helper was named after the resource pluralized.**
|
|
@@ -6133,9 +6255,9 @@ Changed.
|
|
|
6133
6255
|
:photos, as: :image` named it `images`, neither of which any app defines.
|
|
6134
6256
|
Rails builds the collection name from the `as:` value, or the resource name,
|
|
6135
6257
|
exactly as written, and appends `_index` when that name is already singular.
|
|
6136
|
-
The static walker does the same now, so
|
|
6137
|
-
|
|
6138
|
-
|
|
6258
|
+
The static walker does the same now, so `resources :following` reports
|
|
6259
|
+
`following_index`. A collection block under a singular `resource` stays
|
|
6260
|
+
singular.
|
|
6139
6261
|
- **`controllers` with `detail:"full"` crashed on an app whose controller had
|
|
6140
6262
|
two strong-params methods under a parent other than
|
|
6141
6263
|
`ApplicationController`.** The grouping fingerprint sorted the hashes the
|
|
@@ -6172,7 +6294,7 @@ Changed.
|
|
|
6172
6294
|
still taken as written.
|
|
6173
6295
|
- **A controller with no public actions rendered as a name and a trailing
|
|
6174
6296
|
dash.** The listing and `analyze_feature` now say `(no public actions)`; on
|
|
6175
|
-
|
|
6297
|
+
one app that covers six base controllers.
|
|
6176
6298
|
- **A controller file the walk could not read was listed as a controller with
|
|
6177
6299
|
no actions.** The listings rendered an entry carrying an error as `(no
|
|
6178
6300
|
public actions)`, and the summary listing as `0 actions`, while asking for
|
|
@@ -6182,7 +6304,7 @@ Changed.
|
|
|
6182
6304
|
in.** `rails_get_controllers(detail: "full")` built the heading from the
|
|
6183
6305
|
first segment of the first member's name, so five top-level controllers were
|
|
6184
6306
|
filed under `CustomCssController::*` and `OAuth::UserinfoController` under
|
|
6185
|
-
`Api::*`; 38 of
|
|
6307
|
+
`Api::*`; 38 of one app's 309 controllers appeared only inside such a
|
|
6186
6308
|
heading and their real constant was nowhere in the answer. The heading now
|
|
6187
6309
|
names the longest namespace every member shares, or just the count when they
|
|
6188
6310
|
share none, and the group lists every member by its full constant.
|
|
@@ -6198,7 +6320,7 @@ Changed.
|
|
|
6198
6320
|
skips it, while the booted tier struck the same filter through. The record
|
|
6199
6321
|
now carries the skip, one renderer draws the listing for the tool and the
|
|
6200
6322
|
generated markdown, and an ancestor's own skip no longer reaches a child as
|
|
6201
|
-
an inherited filter -
|
|
6323
|
+
an inherited filter - one app's Api::V1 controllers listed
|
|
6202
6324
|
`require_functional!` that way. A skip that names `only:` or `except:`
|
|
6203
6325
|
covers those actions only, so an action the skip does not name still lists
|
|
6204
6326
|
the filter it runs. A skip naming actions also keeps the filter in every
|
|
@@ -6207,13 +6329,12 @@ Changed.
|
|
|
6207
6329
|
through as if it never ran.
|
|
6208
6330
|
- **A per-action filter chain dropped a filter whose only evidence was a skip
|
|
6209
6331
|
constrained to other actions.** The whole-controller answer kept it and
|
|
6210
|
-
named the actions it loses, so one tool contradicted itself on one tier:
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
wins.
|
|
6332
|
+
named the actions it loses, so one tool contradicted itself on one tier: an
|
|
6333
|
+
`Auth::RegistrationsController` said `check_self_destruct! (skipped on:
|
|
6334
|
+
edit, update)` for the class and said nothing at all for `new`. A skip whose
|
|
6335
|
+
`only:`/`except:` leaves the queried action alone now reads as evidence the
|
|
6336
|
+
filter runs there, with no skipped tail on the row. A real `if:`/`unless:`
|
|
6337
|
+
on the same name, on the class or on an ancestor, still wins.
|
|
6217
6338
|
- **A skip of an inherited filter inverted the per-action answer.** The booted
|
|
6218
6339
|
payload read a `skip_before_action :authenticate!, only: [ :index ]` as a
|
|
6219
6340
|
constraint on the filter itself, so `authenticate!` was named on the one
|
|
@@ -6260,7 +6381,7 @@ Changed.
|
|
|
6260
6381
|
- **The full controller listing grouped unrelated controllers by the parent's
|
|
6261
6382
|
source spelling.** The static walk stores a superclass as written, so two
|
|
6262
6383
|
namespaces that each define a `BaseController` looked identical to the group
|
|
6263
|
-
key while the rendered chain resolved it. On
|
|
6384
|
+
key while the rendered chain resolved it. On one app,
|
|
6264
6385
|
`Admin::DashboardController` and seven `Settings::Exports` controllers
|
|
6265
6386
|
shared one group headed by a constant the app does not define, and the seven
|
|
6266
6387
|
were given a filter chain that is not theirs. The key and the `Inherits:`
|
|
@@ -6270,21 +6391,21 @@ Changed.
|
|
|
6270
6391
|
- **The full controller listing copied the whole introspection payload once
|
|
6271
6392
|
per controller.** Resolving a parent reached for the shared cache inside the
|
|
6272
6393
|
loop, and every read of that cache deep-copies the entire context under a
|
|
6273
|
-
mutex. On
|
|
6394
|
+
mutex. On an app with 309 controllers the `--detail full` listing made 608
|
|
6274
6395
|
copies and spent 8.2 of its 8.6 seconds in them. The listing reads the
|
|
6275
6396
|
payload once and passes it down. Rendered output is unchanged and the run
|
|
6276
6397
|
takes 4.3 seconds.
|
|
6277
6398
|
- **`rails_get_schema` and `rails_get_routes` copied the whole payload once
|
|
6278
6399
|
per row.** Every read of the shared cache deep-copies the introspection
|
|
6279
6400
|
payload under a mutex, and both listings read it inside their loop, over
|
|
6280
|
-
tables and over controller headings.
|
|
6401
|
+
tables and over controller headings. One app's 116 tables cost 350 copies
|
|
6281
6402
|
and 7.5 of an 8.1 second run; its 657 static routes cost 297 copies and 6.0
|
|
6282
6403
|
of 6.4 seconds. Both read the payload once and pass it down now, and the
|
|
6283
6404
|
runs take 5.5 and 4.3 seconds with the rendered output unchanged.
|
|
6284
6405
|
- **The same copy-per-row shape was in `rails_get_callbacks` and a
|
|
6285
6406
|
`rails_search_code` trace.** The full callback listing resolved the model
|
|
6286
6407
|
file through the shared cache once per callback of every model, 152 copies
|
|
6287
|
-
on
|
|
6408
|
+
on one app, and a trace read it once per controller file among the call
|
|
6288
6409
|
sites, 70 for `current_user`. Each reads once for the whole listing now, and
|
|
6289
6410
|
a single-controller `rails_get_controllers` request went from five reads to
|
|
6290
6411
|
one.
|
|
@@ -6310,12 +6431,12 @@ Changed.
|
|
|
6310
6431
|
- **Static `get_api` named serializers by camelizing the file path, so an app
|
|
6311
6432
|
acronym came out miscased.** `ActivityPub::AcceptFollowSerializer` was
|
|
6312
6433
|
reported as `Activitypub::AcceptFollowSerializer` and
|
|
6313
|
-
`REST::AccountSerializer` as `Rest::AccountSerializer`; 137 of
|
|
6314
|
-
|
|
6315
|
-
|
|
6434
|
+
`REST::AccountSerializer` as `Rest::AccountSerializer`; 137 of one app's 144
|
|
6435
|
+
names were wrong. `camelize` reads the global inflector, and the static tier
|
|
6436
|
+
never loads the app's `acronym` registrations. The list now comes from
|
|
6316
6437
|
`SourceScan.classes`, which reads the class each file declares, so it needs
|
|
6317
6438
|
no inflector, covers packs and in-repo engines, and stops walking
|
|
6318
|
-
`app/serializers/concerns` (
|
|
6439
|
+
`app/serializers/concerns` (that app goes from 144 names to 143).
|
|
6319
6440
|
`MarkdownSerializer#api` writes this list into the generated context file,
|
|
6320
6441
|
so the wrong names were committed into apps' context, not only printed. The
|
|
6321
6442
|
jbuilder count in the same method now goes through `PathResolver.view_dirs`
|
|
@@ -6329,14 +6450,14 @@ Changed.
|
|
|
6329
6450
|
app's.** `rails_migration_advisor` read `Rails.version` from its own
|
|
6330
6451
|
process, so a standalone `--no-boot` run had no such constant and every
|
|
6331
6452
|
migration came out as `ActiveRecord::Migration[7.1]`, while a run inside a
|
|
6332
|
-
bundle reported whichever Rails the gem had loaded.
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6453
|
+
bundle reported whichever Rails the gem had loaded. An app on 8.1.3.1 got
|
|
6454
|
+
7.1, and that bracket is not cosmetic: it selects a migration compatibility
|
|
6455
|
+
mode. The version now comes from the app's context, the same hash the tool
|
|
6456
|
+
already reads for the database adapter, so both tiers stamp the app's own
|
|
6457
|
+
major.minor. When neither the context nor a loaded Rails names a version,
|
|
6458
|
+
the tool stamps its supported floor and says so in a Note line above the
|
|
6459
|
+
code. That note states only what the tool observed, not a cause it never
|
|
6460
|
+
checked.
|
|
6340
6461
|
- **`migration_advisor` printed an empty "Affected Models" section for any
|
|
6341
6462
|
model whose table is not the camelized form of its class.** The section
|
|
6342
6463
|
camelized the table back into a model name, so `Admin::ActionLog`
|
|
@@ -6359,11 +6480,10 @@ Changed.
|
|
|
6359
6480
|
bearer-token TODO there too instead of a fixture `sign_in`.
|
|
6360
6481
|
- **A generated request spec raised `UnknownAttributeError` before it could
|
|
6361
6482
|
fail a validation.** `generate_test` built its placeholder record out of the
|
|
6362
|
-
controller's permitted params, so on
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
params still carry every permitted name.
|
|
6483
|
+
controller's permitted params, so on an `Api::V1::AccountsController` eight
|
|
6484
|
+
of the nine keys handed to `Account.create!` were not columns. The record is
|
|
6485
|
+
built from the permitted names that are columns, a TODO names the ones left
|
|
6486
|
+
out, and the request params still carry every permitted name.
|
|
6367
6487
|
- **`rails_generate_test` rendered an empty `describe "associations" do end`
|
|
6368
6488
|
in the static tier.** The rspec branch matched the macro against
|
|
6369
6489
|
`"belongs_to"` and its siblings while the static walk reported it as a
|
|
@@ -6396,7 +6516,7 @@ Changed.
|
|
|
6396
6516
|
files, an app with no users fixture gets a TODO instead of a name, and an
|
|
6397
6517
|
app with no fixtures gets `ModelName.new`.
|
|
6398
6518
|
- **`get_test_info` counted only the spec directories a hardcoded list
|
|
6399
|
-
named.**
|
|
6519
|
+
named.** One app reported 675 of its 1090 spec files and 6 of its 22 spec
|
|
6400
6520
|
directories; `spec/lib`, `spec/workers`, `spec/serializers` and nine others
|
|
6401
6521
|
had no row at all. Categories are now read from whatever the app keeps under
|
|
6402
6522
|
`spec/` and `test/`: one row per top-level directory holding test files, a
|
|
@@ -6413,7 +6533,7 @@ Changed.
|
|
|
6413
6533
|
word character, which also fixes `--exact-match` on `@user`, `match_type:
|
|
6414
6534
|
"definition"` and `"class"` on predicate and bang names, trace mode listing
|
|
6415
6535
|
`presave!` as a call site of `save!`, and method source lookup for `[]`,
|
|
6416
|
-
`foo=` and `<=>`. Against
|
|
6536
|
+
`foo=` and `<=>`. Against one app, `def reblog?` now reports the 3 lines
|
|
6417
6537
|
ripgrep finds instead of 6.
|
|
6418
6538
|
- **`format: "json"` on `rails_get_schema` returned markdown.** The parameter
|
|
6419
6539
|
is declared, schema-validated and accepted without a warning, but only a
|
|
@@ -6433,11 +6553,11 @@ Changed.
|
|
|
6433
6553
|
when the cap was reached instead of passing the cap off as a total. The page
|
|
6434
6554
|
size is sized in matches, the pagination hint and the per-file group
|
|
6435
6555
|
headings carry the same two facts, and trace mode marks a caller list that
|
|
6436
|
-
stopped at the cap. Against
|
|
6437
|
-
reports 3 matches rather than 30, and `def call` reports its 103 matches
|
|
6438
|
-
the context lines no longer fill the cap. At the default two lines of
|
|
6439
|
-
the same search reaches the cap at 40 and says so, which is the
|
|
6440
|
-
can honestly claim rather than a total.
|
|
6556
|
+
stopped at the cap. Against one app, `def reblog?` with `--context-lines 5`
|
|
6557
|
+
reports 3 matches rather than 30, and `def call` reports its 103 matches
|
|
6558
|
+
once the context lines no longer fill the cap. At the default two lines of
|
|
6559
|
+
context the same search reaches the cap at 40 and says so, which is the
|
|
6560
|
+
floor the cap can honestly claim rather than a total.
|
|
6441
6561
|
- **A ripgrep run that failed, or that recovered from an unreadable file,
|
|
6442
6562
|
answered wrong.** The exit status of the `rg` run was discarded, so a run
|
|
6443
6563
|
that failed outright looked the same as a run that matched nothing, and a
|
|
@@ -6472,7 +6592,7 @@ Changed.
|
|
|
6472
6592
|
- **Static `get_i18n` reported the locale files as the app's available
|
|
6473
6593
|
locales.** Rails builds `available_locales` from `config/locales` only while
|
|
6474
6594
|
the app leaves the setting alone; once it assigns
|
|
6475
|
-
`config.i18n.available_locales`, that list is the answer.
|
|
6595
|
+
`config.i18n.available_locales`, that list is the answer. One app ships
|
|
6476
6596
|
files for 106 locales and enables 97, so nine disabled locales were listed
|
|
6477
6597
|
as available, seven got their own coverage row, and asking for one of them
|
|
6478
6598
|
by name answered with a full locale report. The static answer now reads the
|
|
@@ -6590,8 +6710,8 @@ Changed.
|
|
|
6590
6710
|
uses, was not. The banner prints after the listener is running.
|
|
6591
6711
|
- **An app that exits during boot answered nothing at all.** The binary set
|
|
6592
6712
|
`RAILS_ENV` only when `--environment` was passed, so the documented default
|
|
6593
|
-
never reached apps that read the variable in `config/boot.rb`.
|
|
6594
|
-
|
|
6713
|
+
never reached apps that read the variable in `config/boot.rb`. Some apps
|
|
6714
|
+
abort there, and an abort during boot was the one failure mode the static
|
|
6595
6715
|
tier did not cover, so the run ended with exit 1 and an empty stdout. The
|
|
6596
6716
|
binary fills in `development` when nothing else set it, and an initializer's
|
|
6597
6717
|
`exit` degrades to the static tier like every other boot failure. The rake
|
|
@@ -6714,7 +6834,7 @@ Changed.
|
|
|
6714
6834
|
`ENV \` followed by indented `KEY=value` lines declared no variables, and
|
|
6715
6835
|
`ENV A=1 B=2` kept only the first name with the rest swallowed into its
|
|
6716
6836
|
value. `env --detail full` now joins continuations into whole instructions
|
|
6717
|
-
and gives each assignment its own row. Against
|
|
6837
|
+
and gives each assignment its own row. Against one app the Dockerfile
|
|
6718
6838
|
section names all 13 `ENV` variables where it named none before.
|
|
6719
6839
|
- **The gems payload said the app had no Ruby version.** The lockfile reader
|
|
6720
6840
|
required exactly three spaces before the `ruby` line of the `RUBY VERSION`
|
|
@@ -6798,7 +6918,7 @@ Changed.
|
|
|
6798
6918
|
itself with a number that did not reconcile with the files under `app/views`
|
|
6799
6919
|
the way the payload listing's does.
|
|
6800
6920
|
- **Generated context files never said they were produced without booting.** A
|
|
6801
|
-
static run writes different numbers (111 models against 114 on
|
|
6921
|
+
static run writes different numbers (111 models against 114 on one app, 657
|
|
6802
6922
|
routes against 723) and nothing in CLAUDE.md, AGENTS.md, the rules files or
|
|
6803
6923
|
`.ai-context.json` let a reader tell which they had. The context records the
|
|
6804
6924
|
tier it was answered in, every generated file with a header carries one
|
|
@@ -6878,7 +6998,7 @@ Changed.
|
|
|
6878
6998
|
`ApplicationRecord` was not walked at all. The booted tier listed the base's
|
|
6879
6999
|
concerns off the ancestor chain while the static tier listed none, which is
|
|
6880
7000
|
two answers to one question. Both tiers now walk every class the model
|
|
6881
|
-
inherits from, up to `ActiveRecord::Base`. On
|
|
7001
|
+
inherits from, up to `ActiveRecord::Base`. On one app that is 111 of 111
|
|
6882
7002
|
models naming `Remotable`, which `ApplicationRecord` includes, against 51
|
|
6883
7003
|
before; the model count, the tables, the callbacks and the scopes are
|
|
6884
7004
|
unchanged.
|
|
@@ -7032,13 +7152,13 @@ duplicated mechanisms behind them.
|
|
|
7032
7152
|
lives, so one in `config/application.rb` or an environment file, which runs
|
|
7033
7153
|
before the load, keeps them too.
|
|
7034
7154
|
- **The static tier named the app after its directory.** `onboard` and every
|
|
7035
|
-
generated context file were headed "
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7155
|
+
generated context file were headed "social" for an app that declares `module
|
|
7156
|
+
Social`. The name now comes from the module enclosing `class Application <
|
|
7157
|
+
Rails::Application` in `config/application.rb`, and falls back to the
|
|
7158
|
+
directory only when that file names nothing.
|
|
7039
7159
|
- **The static tier counted every class under `app/models` as a model.**
|
|
7040
7160
|
Namespace modules, form objects, filters and plain service classes were
|
|
7041
|
-
listed and rendered as models of a table (
|
|
7161
|
+
listed and rendered as models of a table (one app answered 195 models
|
|
7042
7162
|
against 117 booted). A static model is now a class whose superclass chain
|
|
7043
7163
|
reaches `ApplicationRecord`, a namespaced `*ApplicationRecord` or
|
|
7044
7164
|
`ActiveRecord::Base`, STI subclasses included.
|
|
@@ -7255,8 +7375,8 @@ duplicated mechanisms behind them.
|
|
|
7255
7375
|
|
|
7256
7376
|
### Fixed
|
|
7257
7377
|
|
|
7258
|
-
Defects found by a QA round against
|
|
7259
|
-
|
|
7378
|
+
Defects found by a QA round against five large open-source Rails apps. Every
|
|
7379
|
+
one of them exits 0.
|
|
7260
7380
|
|
|
7261
7381
|
- **The commands that read the app degrade the way `tool` does.** `context`,
|
|
7262
7382
|
`inspect`, `facts`, `preset`, `watch` and `init` called a bare boot guard,
|
|
@@ -7270,18 +7390,18 @@ Discourse and Mastodon. Every one of them exits 0.
|
|
|
7270
7390
|
the context writer asked `Rails.application` for the output directory,
|
|
7271
7391
|
which raises `NameError` on the path where Rails is never loaded at all.
|
|
7272
7392
|
- **`tool --list` reads the app's config when the app cannot boot.** Boot is
|
|
7273
|
-
what normally loads `.rails-ai-context.yml`, so the listing fell back to
|
|
7274
|
-
|
|
7275
|
-
offered 43 and the CLI itself answered `Unknown tool 'query'` for one it
|
|
7276
|
-
|
|
7277
|
-
- **`search_extensions` is documented as the fallback's list, which is what
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
- **A table the replay cannot name is not reported.**
|
|
7393
|
+
what normally loads `.rails-ai-context.yml`, so the listing fell back to the
|
|
7394
|
+
gem's defaults: on one app it advertised 45 tools while the MCP server
|
|
7395
|
+
offered 43 and the CLI itself answered `Unknown tool 'query'` for one it had
|
|
7396
|
+
just listed.
|
|
7397
|
+
- **`search_extensions` is documented as the fallback's list, which is what it
|
|
7398
|
+
is.** Making ripgrep honour it did make the two backends agree, and it cost
|
|
7399
|
+
the reach that makes the tool useful: on one app `gem 'devise'` went from 5
|
|
7400
|
+
results to none, because a Gemfile carries no listed extension - and the
|
|
7401
|
+
same for a Rakefile, a `.md`, a `.sql`. The docs, the attr comment and the
|
|
7402
|
+
line the install generator writes now say plainly that the list is the Ruby
|
|
7403
|
+
fallback's and that ripgrep searches every file.
|
|
7404
|
+
- **A table the replay cannot name is not reported.** One app has a migration
|
|
7285
7405
|
that calls `create_table table_name do |t|` with a local computed at run
|
|
7286
7406
|
time; the replay kept the entry under a nil key, and the first serializer
|
|
7287
7407
|
to sort the table names took the whole context run down. Reachable only
|
|
@@ -7290,7 +7410,7 @@ Discourse and Mastodon. Every one of them exits 0.
|
|
|
7290
7410
|
`default_locale` the app configures but ships no file for, every locale
|
|
7291
7411
|
scored zero and all of them were named as untranslated.
|
|
7292
7412
|
- **A mixin in a nested concerns directory is not a model.** The skip only saw
|
|
7293
|
-
the top-level `concerns/` that Rails autoloads, so
|
|
7413
|
+
the top-level `concerns/` that Rails autoloads, so one app's
|
|
7294
7414
|
`app/models/queries/operators/concerns` contributed four mixins to a model
|
|
7295
7415
|
count of 978 where the app has 974. A nested `concerns/` is an ordinary
|
|
7296
7416
|
namespace, though, so the directory name alone does not decide it: a file
|
|
@@ -7302,42 +7422,42 @@ Discourse and Mastodon. Every one of them exits 0.
|
|
|
7302
7422
|
A guard spec derives all three lists from `INTROSPECTOR_MAP`.
|
|
7303
7423
|
|
|
7304
7424
|
- **A controller is named by the constant its source declares.** Zeitwerk
|
|
7305
|
-
resolves a path through the app's own inflector, which the static tier
|
|
7306
|
-
|
|
7307
|
-
controllers
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7425
|
+
resolves a path through the app's own inflector, which the static tier never
|
|
7426
|
+
loads, so camelizing the path invented `Activitypub::` for the 12
|
|
7427
|
+
controllers an app declares as `ActivityPub::` and `Oauth::` for the 4 it
|
|
7428
|
+
declares as `OAuth::` - names that appear nowhere in an app that registers
|
|
7429
|
+
those acronyms, and a `NameError` for anyone who uses one. The path stays
|
|
7430
|
+
the answer where the source does not carry the whole name
|
|
7311
7431
|
(`application_cable/channel.rb`) and where the declared name does not name
|
|
7312
7432
|
that file, since Prism recovers a syntax error into a partial tree.
|
|
7313
7433
|
- **An interceptor is told from a mailer by its framework hook.**
|
|
7314
7434
|
`app/mailers` is also where ActionMailer interceptors live, and every `.rb`
|
|
7315
|
-
under it counted:
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
`delivered_email`; requiring a class instead would have dropped
|
|
7435
|
+
under it counted: An `Interceptors::DefaultHeaders` arrived as a mailer with
|
|
7436
|
+
`delivering_email` - a hook the framework calls - listed as an email an
|
|
7437
|
+
agent could send, 2 of its 11 entries. What an interceptor has and a mailer
|
|
7438
|
+
does not is one of `delivering_email`, `previewing_email` or
|
|
7439
|
+
`delivered_email`; requiring a class instead would have dropped one app's 20
|
|
7320
7440
|
`Emails::*` modules, which hold every notification it sends.
|
|
7321
|
-
- **Migration replay reads what a migration does, not what it undoes.** A
|
|
7322
|
-
|
|
7441
|
+
- **Migration replay reads what a migration does, not what it undoes.** A `def
|
|
7442
|
+
down` says how to reverse the change, so replaying it alongside `up`
|
|
7323
7443
|
cancelled the migration out. On a migrations-only app the ordinary
|
|
7324
|
-
up-creates/down-drops pair erased two tables
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7444
|
+
up-creates/down-drops pair erased two tables the app really has (35 to 37),
|
|
7445
|
+
and the reverse pair would have invented one it dropped. The same now holds
|
|
7446
|
+
for the block spellings, `reversible { |dir| dir.down { ... } }` and
|
|
7447
|
+
`revert`, and for the `t.timestamps` inside them - those were found by a
|
|
7448
|
+
separate walk over the whole file, so a down body's timestamps landed on the
|
|
7449
|
+
last table created on the way up.
|
|
7330
7450
|
- **i18n coverage groups the locales below its own rounding floor instead of
|
|
7331
7451
|
listing them.** A language-name lookup table under `config/locales`
|
|
7332
7452
|
contributes a top-level key per language, and Rails does load each as an
|
|
7333
7453
|
available locale. Scoring them produced a row each saying every key was
|
|
7334
|
-
missing: on
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7454
|
+
missing: on one app, 138 of 186 coverage rows described a translation effort
|
|
7455
|
+
nobody had started. Those 138 are now summarised in one line, with a shared
|
|
7456
|
+
key count stated once. The rows still exist - asking for one by name answers
|
|
7457
|
+
with its numbers, so a translation genuinely started below the floor is not
|
|
7458
|
+
written off as untranslated. The available list still matches a booted
|
|
7459
|
+
Rails, and a locale's keys are read from its own file and any shared file
|
|
7460
|
+
together, so one that lives outside the naming convention is scored.
|
|
7341
7461
|
- **A recorded tool selection no longer switches off `.ai-context.json`.**
|
|
7342
7462
|
The install generator always records one, and every later `rails
|
|
7343
7463
|
ai:context` passed that list to the serializer. The machine artifact is
|
|
@@ -7358,21 +7478,21 @@ Discourse and Mastodon. Every one of them exits 0.
|
|
|
7358
7478
|
asks Ruby where the class was defined - and the consumers read it through
|
|
7359
7479
|
`Payload`.
|
|
7360
7480
|
- **The locale files are indexed once, not once per locale.** Asking every
|
|
7361
|
-
file about every locale is O(locales x files): on
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7481
|
+
file about every locale is O(locales x files): on one app, 187 over 108, it
|
|
7482
|
+
took the i18n answer from 4.6 seconds to four and a half minutes. Each entry
|
|
7483
|
+
also records the locales it serves, so asking for one by name finds its
|
|
7484
|
+
files even when the filename spells the gem rather than the locale.
|
|
7365
7485
|
|
|
7366
7486
|
- **Every consumer that turns a controller name back into a path reads the
|
|
7367
7487
|
file it was read from.** Carrying `file:` fixed six of them and left five
|
|
7368
|
-
answering a confident negative, which is the answer an agent acts on
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7488
|
+
answering a confident negative, which is the answer an agent acts on without
|
|
7489
|
+
checking. On one app's 18 inflected controllers: `rails_test_info` reported
|
|
7490
|
+
16 specs that exist as missing, `rails_get_routes` reported 4 routes that
|
|
7491
|
+
exist as absent, `rails_get_view` 2 views, and `rails_generate_test` wrote a
|
|
7492
|
+
spec whose body was `skip "no routes found"` for a controller with a route.
|
|
7493
|
+
`rails_analyze_feature` listed them as untested.
|
|
7494
|
+
`Payload.controller_route_key` is the one derivation now, and a spec drives
|
|
7495
|
+
all five tools with an inflected name.
|
|
7376
7496
|
- **The install path's files require their own stdlib.** `init` loads six
|
|
7377
7497
|
files before Rails and before the entry file, on purpose - so the entry
|
|
7378
7498
|
file's `require "set"` never runs for them. `legacy_cleanup`'s `[...].to_set`
|
|
@@ -7384,22 +7504,22 @@ Discourse and Mastodon. Every one of them exits 0.
|
|
|
7384
7504
|
- the shape this gem has shipped as a defect before. Entries and size are
|
|
7385
7505
|
now facts, and a store that answers `#stats` gets its hash rendered as pairs
|
|
7386
7506
|
rather than a Ruby literal.
|
|
7387
|
-
- **A model's table comes from its file, not from its name.** Naming a model
|
|
7388
|
-
the constant its source declares made `underscore` stop being the way
|
|
7389
|
-
`OAuthClientConfig` underscores to `o_auth_client_config`, and the
|
|
7390
|
-
`oauth_client_config.rb`. `rails_model_details` reported the table
|
|
7391
|
-
`o_auth_client_configs`, which no app has, so the columns section
|
|
7392
|
-
and the schema hint pointed at nothing - on
|
|
7507
|
+
- **A model's table comes from its file, not from its name.** Naming a model
|
|
7508
|
+
by the constant its source declares made `underscore` stop being the way
|
|
7509
|
+
back: `OAuthClientConfig` underscores to `o_auth_client_config`, and the
|
|
7510
|
+
file is `oauth_client_config.rb`. `rails_model_details` reported the table
|
|
7511
|
+
as `o_auth_client_configs`, which no app has, so the columns section
|
|
7512
|
+
vanished and the schema hint pointed at nothing - on one app that hit
|
|
7393
7513
|
`OAuthClientConfig`, `OAuthRequest`, `AuthenticationProvider::OAuth` and
|
|
7394
|
-
`OAuth2`, on
|
|
7514
|
+
`OAuth2`, on another `OAuthClient` and `OAuthClientToken`. The file's own
|
|
7395
7515
|
name already carries the inflection, because Zeitwerk resolved the constant
|
|
7396
7516
|
from it. `rails_generate_test`, `rails_get_callbacks`, `rails_test_info` and
|
|
7397
7517
|
the five `rails_validate` checks that key models by path read it too.
|
|
7398
7518
|
- **An abstract base class is not a model in the static tier either.** A
|
|
7399
7519
|
booted Rails rejects `abstract_class?`, and a namespaced base class is one:
|
|
7400
|
-
|
|
7520
|
+
`Ci::ApplicationRecord`, `PackageMetadata::ApplicationRecord` and
|
|
7401
7521
|
`SecApplicationRecord` were counted, so the same app answered 895 models
|
|
7402
|
-
without a boot and 888 with one.
|
|
7522
|
+
without a boot and 888 with one. Another app: 974 against 971.
|
|
7403
7523
|
- **A model carries its file too, and is named by the constant its source
|
|
7404
7524
|
declares.** `rails_model_details` rebuilt `app/models/<underscored>.rb` from
|
|
7405
7525
|
the name in four places, so for a model in a pack or an engine the custom
|
|
@@ -7413,13 +7533,13 @@ Discourse and Mastodon. Every one of them exits 0.
|
|
|
7413
7533
|
gave a constant the app never declares and the undefined-ivar check stopped
|
|
7414
7534
|
running for that whole tree without saying so.
|
|
7415
7535
|
- **A test-gap claim is not made from a scan that stopped early.**
|
|
7416
|
-
`rails_analyze_feature` scans the first 500 files per glob;
|
|
7417
|
-
1,672 specs
|
|
7418
|
-
|
|
7419
|
-
|
|
7536
|
+
`rails_analyze_feature` scans the first 500 files per glob; on an app with
|
|
7537
|
+
1,672 specs the scan never reached the ones covering the feature and every
|
|
7538
|
+
controller in it was reported as having no test. The Tests section disclosed
|
|
7539
|
+
the cap, the gaps section asserted through it.
|
|
7420
7540
|
- **"Global before_actions" in the generated files means global.** The scan
|
|
7421
7541
|
matched `skip_before_action` too, and ignored `only:` / `except:` / `if:`.
|
|
7422
|
-
|
|
7542
|
+
One app's `CLAUDE.md` listed five, of which three were false - including
|
|
7423
7543
|
`verify_authenticity_token`, which that controller skips.
|
|
7424
7544
|
|
|
7425
7545
|
### Added
|
|
@@ -7622,7 +7742,7 @@ Discourse and Mastodon. Every one of them exits 0.
|
|
|
7622
7742
|
- **A model's `*_url` and `*_path` columns are not reported as missing route
|
|
7623
7743
|
helpers.** `rails_validate` read every receiverless call ending in `_url` as
|
|
7624
7744
|
a route helper, and an attribute reader has no `def` for the local-method
|
|
7625
|
-
escape to find, so `shared_inbox_url` on
|
|
7745
|
+
escape to find, so `shared_inbox_url` on an `accounts` table warned as a
|
|
7626
7746
|
broken route. The check now consults the model's own column list. (#143)
|
|
7627
7747
|
|
|
7628
7748
|
## [5.21.2] - 2026-08-12
|