rails-ai-context 5.36.9 → 5.37.0
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 +1061 -0
- data/README.md +1 -1
- data/app/controllers/rails_ai_context/mcp_controller.rb +0 -10
- data/docs/ARCHITECTURE.md +1 -1
- data/docs/CLI.md +7 -1
- data/docs/COMPATIBILITY.md +21 -0
- data/docs/CONFIGURATION.md +1 -1
- data/docs/CUSTOM_TOOLS.md +2 -2
- data/docs/FAQ.md +1 -1
- data/docs/GUIDE.md +3 -3
- data/docs/INTROSPECTORS.md +10 -7
- data/docs/PROMPTS.md +1 -1
- data/docs/SECURITY.md +1 -1
- data/docs/SETUP.md +2 -2
- data/docs/TOOLS.md +11 -4
- data/docs/adr/0005-workspace-mcp-entries.md +1 -1
- data/exe/rails-ai-context +39 -8
- data/lib/generators/rails_ai_context/install/install_generator.rb +12 -12
- data/lib/rails_ai_context/action_filters.rb +1 -2
- data/lib/rails_ai_context/app_databases.rb +2 -2
- data/lib/rails_ai_context/app_inflections.rb +59 -0
- data/lib/rails_ai_context/base_mixins.rb +3 -3
- data/lib/rails_ai_context/brackets.rb +11 -0
- data/lib/rails_ai_context/change_watch.rb +2 -1
- data/lib/rails_ai_context/cli/entry_boot.rb +14 -2
- data/lib/rails_ai_context/cli/tool_runner.rb +19 -4
- data/lib/rails_ai_context/code_reloader.rb +1 -1
- data/lib/rails_ai_context/concern_macros.rb +46 -60
- data/lib/rails_ai_context/concern_membership.rb +10 -13
- data/lib/rails_ai_context/concern_paths.rb +132 -92
- data/lib/rails_ai_context/confidence.rb +8 -0
- data/lib/rails_ai_context/database_family/sqlite_runtime.rb +1 -1
- data/lib/rails_ai_context/database_family.rb +9 -4
- data/lib/rails_ai_context/doctor.rb +15 -20
- data/lib/rails_ai_context/facts_formatter.rb +4 -14
- data/lib/rails_ai_context/fingerprinter/snapshot.rb +3 -3
- data/lib/rails_ai_context/fingerprinter.rb +65 -21
- data/lib/rails_ai_context/gem_lock.rb +5 -0
- data/lib/rails_ai_context/hydration_result.rb +3 -3
- data/lib/rails_ai_context/hydrators/hydration_formatter.rb +0 -5
- data/lib/rails_ai_context/hydrators/model_hints.rb +6 -14
- data/lib/rails_ai_context/hydrators/view_hydrator.rb +2 -4
- data/lib/rails_ai_context/install/ai_tool.rb +2 -1
- data/lib/rails_ai_context/install/cleanup.rb +48 -21
- data/lib/rails_ai_context/install/program.rb +10 -4
- data/lib/rails_ai_context/install/selection_record.rb +4 -15
- data/lib/rails_ai_context/install/uninstall.rb +8 -2
- data/lib/rails_ai_context/install_mode.rb +1 -1
- data/lib/rails_ai_context/introspector.rb +3 -4
- data/lib/rails_ai_context/introspectors/action_mailbox_introspector.rb +11 -3
- data/lib/rails_ai_context/introspectors/action_presence.rb +2 -2
- data/lib/rails_ai_context/introspectors/action_resolver.rb +0 -10
- data/lib/rails_ai_context/introspectors/active_support_introspector.rb +28 -26
- data/lib/rails_ai_context/introspectors/api_introspector.rb +7 -7
- data/lib/rails_ai_context/introspectors/association_class.rb +108 -0
- data/lib/rails_ai_context/introspectors/ast_walk.rb +18 -1
- data/lib/rails_ai_context/introspectors/auth_introspector.rb +5 -4
- data/lib/rails_ai_context/introspectors/autoload_introspector.rb +1 -3
- data/lib/rails_ai_context/introspectors/base.rb +4 -0
- data/lib/rails_ai_context/introspectors/call_site_expansion.rb +5 -2
- data/lib/rails_ai_context/introspectors/component_introspector.rb +4 -4
- data/lib/rails_ai_context/introspectors/controller_filters.rb +7 -10
- data/lib/rails_ai_context/introspectors/controller_introspector.rb +54 -34
- data/lib/rails_ai_context/introspectors/controller_settings.rb +8 -5
- data/lib/rails_ai_context/introspectors/convention_introspector.rb +19 -19
- data/lib/rails_ai_context/introspectors/credentials_introspector.rb +125 -15
- data/lib/rails_ai_context/introspectors/declared_constant.rb +248 -48
- data/lib/rails_ai_context/introspectors/devops_introspector.rb +11 -11
- data/lib/rails_ai_context/introspectors/eager_load.rb +45 -20
- data/lib/rails_ai_context/introspectors/env_config_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/env_introspector.rb +0 -1
- data/lib/rails_ai_context/introspectors/env_references.rb +16 -1
- data/lib/rails_ai_context/introspectors/frontend_framework_introspector.rb +18 -10
- data/lib/rails_ai_context/introspectors/i18n_introspector.rb +4 -4
- data/lib/rails_ai_context/introspectors/includers.rb +49 -10
- data/lib/rails_ai_context/introspectors/job_introspector.rb +40 -21
- data/lib/rails_ai_context/introspectors/listeners/base_listener.rb +93 -11
- data/lib/rails_ai_context/introspectors/listeners/branch_conditions.rb +5 -1
- data/lib/rails_ai_context/introspectors/listeners/chained_call_listener.rb +1 -1
- data/lib/rails_ai_context/introspectors/listeners/class_definition_listener.rb +6 -1
- data/lib/rails_ai_context/introspectors/listeners/component_structure_listener.rb +1 -0
- data/lib/rails_ai_context/introspectors/listeners/enums_listener.rb +15 -11
- data/lib/rails_ai_context/introspectors/listeners/gemfile_dsl_listener.rb +5 -5
- data/lib/rails_ai_context/introspectors/listeners/grape_api_listener.rb +6 -1
- data/lib/rails_ai_context/introspectors/listeners/inflections_listener.rb +123 -0
- data/lib/rails_ai_context/introspectors/listeners/methods_listener.rb +14 -51
- data/lib/rails_ai_context/introspectors/listeners/migration_dsl_listener.rb +5 -3
- data/lib/rails_ai_context/introspectors/listeners/migration_replay_listener.rb +18 -22
- data/lib/rails_ai_context/introspectors/listeners/mixins_listener.rb +10 -4
- data/lib/rails_ai_context/introspectors/listeners/mount_listener.rb +37 -27
- data/lib/rails_ai_context/introspectors/listeners/owner_scope.rb +84 -21
- data/lib/rails_ai_context/introspectors/listeners/routes_dsl_listener.rb +572 -108
- data/lib/rails_ai_context/introspectors/listeners/routes_walk.rb +108 -0
- data/lib/rails_ai_context/introspectors/listeners/schema_dsl_listener.rb +37 -30
- data/lib/rails_ai_context/introspectors/middleware_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/migration_introspector.rb +16 -3
- data/lib/rails_ai_context/introspectors/migration_replay/helpers.rb +8 -7
- data/lib/rails_ai_context/introspectors/migration_replay/statements.rb +1080 -94
- data/lib/rails_ai_context/introspectors/migration_replay.rb +48 -23
- data/lib/rails_ai_context/introspectors/model_introspector.rb +37 -18
- data/lib/rails_ai_context/introspectors/multi_database_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/n_plus_one_scan.rb +49 -25
- data/lib/rails_ai_context/introspectors/observability_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/performance_introspector.rb +5 -15
- data/lib/rails_ai_context/introspectors/pg_naming.rb +13 -19
- data/lib/rails_ai_context/introspectors/rails_version.rb +22 -0
- data/lib/rails_ai_context/introspectors/rake_task_introspector.rb +3 -3
- data/lib/rails_ai_context/introspectors/render_calls.rb +237 -0
- data/lib/rails_ai_context/introspectors/rendered_record.rb +163 -24
- data/lib/rails_ai_context/introspectors/route_introspector.rb +53 -33
- data/lib/rails_ai_context/introspectors/schema_conventions.rb +33 -2
- data/lib/rails_ai_context/introspectors/schema_introspector.rb +30 -19
- data/lib/rails_ai_context/introspectors/schema_reader.rb +16 -2
- data/lib/rails_ai_context/introspectors/security_introspector.rb +34 -11
- data/lib/rails_ai_context/introspectors/seeds_introspector.rb +13 -5
- data/lib/rails_ai_context/introspectors/source_calls.rb +3 -2
- data/lib/rails_ai_context/introspectors/structure_sql_reader.rb +481 -85
- data/lib/rails_ai_context/introspectors/superclass_chain.rb +1 -1
- data/lib/rails_ai_context/introspectors/table_name.rb +13 -32
- data/lib/rails_ai_context/introspectors/test_introspector.rb +23 -23
- data/lib/rails_ai_context/introspectors/turbo_introspector.rb +24 -12
- data/lib/rails_ai_context/introspectors/view_introspector.rb +9 -25
- data/lib/rails_ai_context/introspectors/view_template_introspector.rb +39 -76
- data/lib/rails_ai_context/legacy_cleanup.rb +7 -5
- data/lib/rails_ai_context/markup_source.rb +455 -0
- data/lib/rails_ai_context/mcp_config_generator.rb +144 -49
- data/lib/rails_ai_context/package_json.rb +8 -0
- data/lib/rails_ai_context/path_resolver.rb +106 -21
- data/lib/rails_ai_context/payload.rb +7 -4
- data/lib/rails_ai_context/percent.rb +1 -1
- data/lib/rails_ai_context/portable_path.rb +0 -4
- data/lib/rails_ai_context/resources.rb +8 -9
- data/lib/rails_ai_context/route_coverage.rb +16 -4
- data/lib/rails_ai_context/run_cache.rb +5 -0
- data/lib/rails_ai_context/safe_file.rb +42 -4
- data/lib/rails_ai_context/safe_path.rb +12 -0
- data/lib/rails_ai_context/secret_columns.rb +2 -6
- data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +12 -7
- data/lib/rails_ai_context/serializers/context_file_serializer.rb +7 -4
- data/lib/rails_ai_context/serializers/copilot_instructions_serializer.rb +1 -1
- data/lib/rails_ai_context/serializers/cursor_rules_serializer.rb +3 -2
- data/lib/rails_ai_context/serializers/opencode_rules_serializer.rb +1 -1
- data/lib/rails_ai_context/serializers/section_marker_writer.rb +16 -14
- data/lib/rails_ai_context/serializers/stack_overview_helper.rb +6 -5
- data/lib/rails_ai_context/server.rb +7 -19
- data/lib/rails_ai_context/sql_gate.rb +16 -12
- data/lib/rails_ai_context/tasks/rails_ai_context.rake +18 -5
- data/lib/rails_ai_context/test_framework.rb +204 -6
- data/lib/rails_ai_context/test_helper.rb +6 -2
- data/lib/rails_ai_context/tools/analyze_feature.rb +39 -68
- data/lib/rails_ai_context/tools/base_tool.rb +51 -12
- data/lib/rails_ai_context/tools/dependency_graph.rb +65 -117
- data/lib/rails_ai_context/tools/generate_test.rb +74 -18
- data/lib/rails_ai_context/tools/get_api.rb +1 -1
- data/lib/rails_ai_context/tools/get_concern.rb +77 -51
- data/lib/rails_ai_context/tools/get_context.rb +3 -6
- data/lib/rails_ai_context/tools/get_controllers.rb +13 -13
- data/lib/rails_ai_context/tools/get_edit_context.rb +13 -18
- data/lib/rails_ai_context/tools/get_env.rb +45 -38
- data/lib/rails_ai_context/tools/get_frontend_stack.rb +2 -5
- data/lib/rails_ai_context/tools/get_gems.rb +1 -1
- data/lib/rails_ai_context/tools/get_helper_methods.rb +26 -28
- data/lib/rails_ai_context/tools/get_partial_interface.rb +46 -141
- data/lib/rails_ai_context/tools/get_routes.rb +9 -8
- data/lib/rails_ai_context/tools/get_schema.rb +8 -3
- data/lib/rails_ai_context/tools/get_stimulus.rb +7 -4
- data/lib/rails_ai_context/tools/get_test_info.rb +75 -27
- data/lib/rails_ai_context/tools/get_turbo_map.rb +5 -4
- data/lib/rails_ai_context/tools/get_view.rb +37 -29
- data/lib/rails_ai_context/tools/migration_advisor.rb +64 -19
- data/lib/rails_ai_context/tools/onboard.rb +6 -1
- data/lib/rails_ai_context/tools/query.rb +1 -1
- data/lib/rails_ai_context/tools/read_logs.rb +1 -2
- data/lib/rails_ai_context/tools/review_changes.rb +40 -41
- data/lib/rails_ai_context/tools/runtime_info.rb +1 -6
- data/lib/rails_ai_context/tools/safe_call.rb +1 -1
- data/lib/rails_ai_context/tools/search_code.rb +1 -1
- data/lib/rails_ai_context/tools/search_docs.rb +6 -2
- data/lib/rails_ai_context/tools/security_scan.rb +2 -2
- data/lib/rails_ai_context/tools/session_context.rb +2 -1
- data/lib/rails_ai_context/tools/validate.rb +2 -0
- data/lib/rails_ai_context/tools/validate_semantics.rb +46 -31
- data/lib/rails_ai_context/update_check.rb +1 -1
- data/lib/rails_ai_context/version.rb +1 -1
- data/lib/rails_ai_context/vfs.rb +5 -5
- data/lib/rails_ai_context/view_file.rb +13 -1
- data/lib/rails_ai_context/watcher.rb +3 -1
- data/lib/rails_ai_context.rb +4 -3
- metadata +8 -1
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,1067 @@ 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.37.0] - 2026-10-25
|
|
9
|
+
|
|
10
|
+
### Security
|
|
11
|
+
|
|
12
|
+
- **Install and uninstall leave a `.gitignore` or initializer that links out of
|
|
13
|
+
the app as it is, and name it.** They wrote through such a link before, and
|
|
14
|
+
the legacy rule-file cleanup deletes nothing a symlinked rules folder carries
|
|
15
|
+
out of the app.
|
|
16
|
+
- **Uninstall and the dropped-tool cleanup no longer delete or edit a file that
|
|
17
|
+
a symlinked directory carries out of the app.** Such a file is left in place
|
|
18
|
+
and named as left behind a symlink the cleanup does not follow.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **The Claude Code plugin needs rails-ai-context 5.36.9 or later**, the first
|
|
23
|
+
release whose `doctor` and update notice know it; an older one sent you to
|
|
24
|
+
`init`, which adds a second server beside the plugin's. With an older gem the
|
|
25
|
+
plugin's server connects with no tools and says to update it.
|
|
26
|
+
- **`rails_validate` with `level: "rails"` has Brakeman parse only the files it
|
|
27
|
+
checks and the app's models, so one file no longer costs a whole-app scan.**
|
|
28
|
+
The models keep a query on a model at the confidence a full scan gives it. On
|
|
29
|
+
a generated app of 30-line models they took one file's scan from 0.02s to
|
|
30
|
+
0.32s at 250 models and from 0.04s to 0.92s at 700 (Brakeman 8.1.0, best of
|
|
31
|
+
3). A view's warning that comes from its controller's params shows when both
|
|
32
|
+
files are passed; `rails_security_scan` still scans the whole app. (#529)
|
|
33
|
+
- **`TestHelper#execute_tool` raises when the tool returns an error, and
|
|
34
|
+
`execute_tool_with_error` fails the test when the call succeeds.** Before,
|
|
35
|
+
both helpers returned whatever the tool answered, so a test on a broken tool
|
|
36
|
+
passed. A suite that read an error text through `execute_tool` should call
|
|
37
|
+
`execute_tool_with_error` instead. (#593)
|
|
38
|
+
- **The env section reads each source file once and keeps its scan until the
|
|
39
|
+
files change.** The ENV scan and the HTTP client check share one read, and the
|
|
40
|
+
result lives beside the shared context, so a repeated call costs milliseconds
|
|
41
|
+
rather than about a second. The read runs without holding the shared cache's
|
|
42
|
+
lock, so other tools do not wait on it. (#597)
|
|
43
|
+
- **Doctor introspects once.** The introspector health check and the context
|
|
44
|
+
files' dry run read the same run, so doctor takes about half the time on a
|
|
45
|
+
large app, and the health check no longer reports introspectors that fail only
|
|
46
|
+
when run alone. (#598)
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **A standalone run on a Windows Ruby reached through a link keeps this gem's
|
|
51
|
+
paths ahead of Ruby's own lib directories.** setup-ruby unpacks Ruby in one
|
|
52
|
+
folder and runs it through a link to it from the tool cache: there RbConfig
|
|
53
|
+
names Ruby's directories by the link's target while the load path names them
|
|
54
|
+
by the link. The paths put back after the app's boot were matched to
|
|
55
|
+
RbConfig's spelling, found none, and went last, behind Ruby's own copies, so a
|
|
56
|
+
gem with a default-gem twin (prism, json) could load half from each. Ruby's
|
|
57
|
+
own entries are now found by the mark Ruby gives them, the one RubyGems
|
|
58
|
+
inserts gem paths by.
|
|
59
|
+
- **A route in a resource block whose path has a dynamic segment takes no name,
|
|
60
|
+
and the resource's own route keeps its name.** A route like
|
|
61
|
+
`delete "groups/:group_id" => "users#remove_group"` inside `resources :users`
|
|
62
|
+
was named after the resource (`user`), so `DELETE /users/:id` lost its name.
|
|
63
|
+
Rails gives such a route no name unless it has `as:`.
|
|
64
|
+
- **A response cut at the size limit ends on a whole line.** It was cut
|
|
65
|
+
mid-line, so the last line could read as half a fact, such as an index named
|
|
66
|
+
on no columns.
|
|
67
|
+
- **A static route whose path ends in a slash or holds `//` gets the helper name
|
|
68
|
+
Rails gives it.** The name is built from the cleaned path, so
|
|
69
|
+
`/reports/daily/` is `reports_daily`, not `reports_daily_`. (#540)
|
|
70
|
+
- **`get_routes` and `analyze_feature` pass a glob segment to the route
|
|
71
|
+
helper.** `get "docs/*section", as: :doc` now reads `doc_path(:section)` with
|
|
72
|
+
`[section]`, since Rails requires the key. Both tiers list the glob among the
|
|
73
|
+
route's params. (#541)
|
|
74
|
+
- **A static verb route named by a symbol takes its URL from `path:`.** `get
|
|
75
|
+
:stats, path: "statistics"` in a member block is `/heroes/:id/statistics`, and
|
|
76
|
+
the helper and action still come from the symbol. A computed `path:` is
|
|
77
|
+
counted as a construct the static tier could not expand. (#542)
|
|
78
|
+
- **Both tiers name the root route when `GET /` also answers another verb or any
|
|
79
|
+
verb.** The static tier gave none for `match "/", via: [:get, :post]`, and the
|
|
80
|
+
booted tier gave none for `via: :all`. (#589)
|
|
81
|
+
- **Static routes read an in-repo engine's routes whatever order the
|
|
82
|
+
introspectors run in.** When the engines section ran first, the routes section
|
|
83
|
+
reported the engine as needing a boot. (#587)
|
|
84
|
+
- **`routes` lists a large table in seconds.** Summary folding and the PATCH/PUT
|
|
85
|
+
merge each rescanned every route per route. A namespace of 2000 controllers
|
|
86
|
+
took 24s and now takes 2s, with the same output. (#601)
|
|
87
|
+
- **Static routes list a resource's block and concern routes before its default
|
|
88
|
+
ones, the order Rails matches them.** The static list put `/heroes/:id` ahead
|
|
89
|
+
of `/heroes/search`, unlike Rails and the booted tier. A member block that
|
|
90
|
+
draws `post :update` no longer splits the resource's own PATCH and PUT update
|
|
91
|
+
routes into two rows. (#528)
|
|
92
|
+
- **Static routes match Rails for a singular resource's bare verb, a resource at
|
|
93
|
+
`path: ""`, and a multi-name `resources` block.** `get :avatar` in `resource
|
|
94
|
+
:profile` is `avatar_profile`, `resources :pages, path: ""` is `/:id`, and
|
|
95
|
+
`resources :users, :groups do ... end` draws the block under each. (#508)
|
|
96
|
+
- **Static routes name a resource nested in a `member` or `collection` block the
|
|
97
|
+
way Rails does.** `resources :posts do member { resources :votes } end` names
|
|
98
|
+
its routes `votes` and `vote_tally`, not `post_votes` and `post_vote_tally`.
|
|
99
|
+
- **A `scope` inside a static resource block goes before the resource's path and
|
|
100
|
+
name, as Rails draws it.** `scope "x"` inside `resource :settings` gives
|
|
101
|
+
`/x/settings/inner`, and `scope "y", as: "why"` inside `resources :posts`
|
|
102
|
+
names `why_post_qux`. A shallow resource's member and nested routes take the
|
|
103
|
+
scope after the shallow path and prefix.
|
|
104
|
+
- **A static route in a `scope` with `as:` inside a `member`, `collection` or
|
|
105
|
+
`new` block keeps that `as:` in its name, as Rails gives it.** `member {
|
|
106
|
+
scope("qq", as: "qux") { get :mm } }` in `resources :heroes` is
|
|
107
|
+
`mm_qux_hero`, not `mm_hero`.
|
|
108
|
+
- **Static routes draw a canonical action given in a member, collection or new
|
|
109
|
+
scope at that scope's path, named without the action.** `get :show, on:
|
|
110
|
+
:member, path: "sh"` in `resources :heroes` is named `hero`, not `show_hero`,
|
|
111
|
+
and without `path:` it is `/heroes/:id`. A singular resource's bare verbs are
|
|
112
|
+
member routes, so `get :index` in `resource :profile` is `/profile`, not
|
|
113
|
+
`/profile/index`.
|
|
114
|
+
- **Static routes give the controller Rails does for a route inside a namespace
|
|
115
|
+
or `scope module:` within a resource block.** `resources :posts do namespace
|
|
116
|
+
:admin do get :foo end end` routes to `admin/posts#foo`, not `posts#foo`.
|
|
117
|
+
- **get_autoload lists an app's custom inflections whatever the inflections
|
|
118
|
+
block calls its parameter.** A rule written as `inflections(:en).acronym "X"`,
|
|
119
|
+
on `_1` or `it`, on a local holding `inflections(:en)`, or inside
|
|
120
|
+
`instance_eval` is listed too, as Rails applies all of them. (#583)
|
|
121
|
+
- **Without a boot, a table and a route helper get the names Rails gives them
|
|
122
|
+
under the app's own inflections.** The static tier now reads the literal
|
|
123
|
+
`irregular`, `plural`, `singular`, `uncountable`, `acronym` and `human` rules
|
|
124
|
+
in the app's initializers and applies them for that run only, in tool answers,
|
|
125
|
+
generated context files and resource reads alike, so `Hero` reads `heroes` and
|
|
126
|
+
`resources :staffers` names `staff_path`. The rules never reach another app,
|
|
127
|
+
another thread or the booted tier. (#521)
|
|
128
|
+
- **Without a boot, a constant's file is the one Zeitwerk loads it from.** With
|
|
129
|
+
`acronym 'JsonLd'`, `include JsonLdHelper` reads
|
|
130
|
+
`app/helpers/json_ld_helper.rb`, found by the file name and not the
|
|
131
|
+
underscored constant, so its filters are no longer reported as not read, and a
|
|
132
|
+
lib module's `helper_method`s are listed when its path splits an app acronym.
|
|
133
|
+
Where only a Zeitwerk inflector override spells a name, the file counts when
|
|
134
|
+
it declares that constant, and when several such files share a name, the one
|
|
135
|
+
that declares it is taken. (#521)
|
|
136
|
+
- **Without a boot, get_mailers names a mailbox and its routing target with the
|
|
137
|
+
app's acronym.** Each mailbox takes the class its file declares, and a route
|
|
138
|
+
takes the mailbox whose file its target names, so an app with
|
|
139
|
+
`inflect.acronym "SMS"` gets `SMSMailbox`, as Action Mailbox and Zeitwerk do,
|
|
140
|
+
even when the acronym is computed in a loop. (#571)
|
|
141
|
+
- **The static tier reads a PostgreSQL unlogged table.** A `CREATE UNLOGGED
|
|
142
|
+
TABLE` in `db/structure.sql` or in a migration's `execute` was missing from
|
|
143
|
+
the schema, though Rails lists it as a table. (#543)
|
|
144
|
+
- **The static tier keeps a float column's limit as Rails dumps it.** A MySQL
|
|
145
|
+
`double` reads as `float, limit: 53` and a PostgreSQL `real` as `float, limit:
|
|
146
|
+
24` (Rails 7.2.2 and later), from structure.sql and from replayed migrations.
|
|
147
|
+
A PostgreSQL `real[]` column reads with limit 24, as Rails reads its element.
|
|
148
|
+
(#546)
|
|
149
|
+
- **A table whose name only starts with `schema_migrations` or
|
|
150
|
+
`ar_internal_metadata` is listed.** Tables such as `schema_migrations_archive`
|
|
151
|
+
were hidden on both tiers, and the static tier called them missing. The booted
|
|
152
|
+
tier now hides the bookkeeping tables under the app's `table_name_prefix` and
|
|
153
|
+
`table_name_suffix`, as SchemaDumper does. `rails_query` no longer treats a
|
|
154
|
+
prefixed `ar_internal_metadata.key` as a secret column. The static schema
|
|
155
|
+
summary no longer reports `schema_migrations` missing when db/schema.rb
|
|
156
|
+
declares it; the declared-table list leaves out the bookkeeping tables, as the
|
|
157
|
+
table list does. (#547)
|
|
158
|
+
- **A hand-written `db/schema.rb` gets its `t.timestamps` columns and index
|
|
159
|
+
names on the static tier.** `created_at` and `updated_at` were missing, and an
|
|
160
|
+
index declared without `name:` had a blank name instead of Rails'
|
|
161
|
+
`index_<table>_on_<columns>`. In a `Schema[4.2]` or older file the timestamps
|
|
162
|
+
allow NULL, as Rails' 4.2 compatibility gives them. (#592)
|
|
163
|
+
- **A `get_schema` JSON page lists only its own tables' check constraints and
|
|
164
|
+
generated columns.** On a large app those app-wide lists filled the response
|
|
165
|
+
cap and pushed out `tables_without_model_file` and `gem_owned_tables`. (#595)
|
|
166
|
+
- **`migration_advisor` writes migrations that run and roll back to the same
|
|
167
|
+
column.** A `references` or `belongs_to` type on `add_column` now writes
|
|
168
|
+
`add_reference` with the key's `type:` and `foreign_key:`, keeping a
|
|
169
|
+
`foreign_key:` that `options` gives, and its strong_migrations hints add no
|
|
170
|
+
key when that is `foreign_key: false`. `change_type`'s `down` and
|
|
171
|
+
`remove_column` carry the column's limit, precision, scale, `array:`, null and
|
|
172
|
+
default, a boolean or number default as its literal, and `create_table` puts
|
|
173
|
+
`options` on its line. `create_table` keeps the app's key type unless the
|
|
174
|
+
options really pass `id:`; a string option that contains "id:" no longer drops
|
|
175
|
+
it. A reference whose table the app does not have (`editor` with no `editors`
|
|
176
|
+
table) is named as a role: the answer says to place it with `add_association`
|
|
177
|
+
and gives the `belongs_to` its model needs, and no longer offers the generator
|
|
178
|
+
line that would add a foreign key to a missing table; with strong_migrations,
|
|
179
|
+
it notes that `add_reference` builds its index without
|
|
180
|
+
`algorithm: :concurrently`. (#507)
|
|
181
|
+
- **`migration_advisor` refuses a `new_name` or `create_table` column name that
|
|
182
|
+
is not a plain identifier, and writes the index `title:string:index` asks
|
|
183
|
+
for.** It returns the same "Invalid column name" error other actions give, and
|
|
184
|
+
`uniq` writes a unique index. `title:index` is an indexed string, and a
|
|
185
|
+
reference's `uniq` writes `index: { unique: true }`, as the Rails generator
|
|
186
|
+
reads them. (#526)
|
|
187
|
+
- **Schema replay leaves an existing column or index alone when `add_column` or
|
|
188
|
+
`add_index` passes `if_not_exists: true`.** The static schema no longer swaps
|
|
189
|
+
the column's type or lists the index twice. From Rails 7.1 an `add_reference`
|
|
190
|
+
keeps an existing column and index the same way on every adapter. An existing
|
|
191
|
+
foreign key or check constraint is skipped under `if_not_exists: true` outside
|
|
192
|
+
SQLite, whose table rebuild adds them again. A check constraint
|
|
193
|
+
exists when its name matches, the given one or the `chk_rails_` name Rails
|
|
194
|
+
derives from the table and expression. A foreign key exists when one to the
|
|
195
|
+
same table matches the way the app's Rails compares them: 7.0 by table alone,
|
|
196
|
+
7.1 and 8.0 also by a column the call gives, and 8.1 by the column, the
|
|
197
|
+
default `<table>_id` when none is given, and a given primary key. (#545)
|
|
198
|
+
- **Schema replay moves foreign keys through `rename_table` and
|
|
199
|
+
`rename_column`.** A key keeps pointing at the renamed table and column, as
|
|
200
|
+
the database's dump shows. A column inside a composite foreign key or
|
|
201
|
+
composite primary key follows the rename too. (#512)
|
|
202
|
+
- **Schema replay drops every table `drop_table` names, and the join table
|
|
203
|
+
`drop_join_table` names.** The static schema no longer lists tables those
|
|
204
|
+
migrations removed. (#544)
|
|
205
|
+
- **Recent migrations list only the schema calls a migration makes.**
|
|
206
|
+
`change_column_null` no longer also reads as `change_column`, `remove_columns`
|
|
207
|
+
reads as `remove_column`, and a name in a comment is not counted. A call on
|
|
208
|
+
the migration's connection still counts, also through a local or a
|
|
209
|
+
`with_connection` block parameter. (#548)
|
|
210
|
+
- **A class built with `::Class.new(...) do ... end` inside a model gets its own
|
|
211
|
+
associations, scopes and validations on the static tier.** They were credited
|
|
212
|
+
to the model around it, and the nested class showed none. (#538)
|
|
213
|
+
- **Static `model_details` lists the values of `enum :status, active: 0,
|
|
214
|
+
archived: 1`.** Rails reads every bare keyword after the name as a value,
|
|
215
|
+
`prefix:` included, and the static tier now does the same, where it used to
|
|
216
|
+
show the source text as computed. (#537)
|
|
217
|
+
- **A concern macro called with `**hash` no longer takes a keyword's default
|
|
218
|
+
when the hash may set it.** The rules that depend on that keyword are listed
|
|
219
|
+
as not decided from the source, unless a literal `key:` pair follows the
|
|
220
|
+
spread. (#579)
|
|
221
|
+
- **The conventions section reads every model file when it detects patterns.**
|
|
222
|
+
An app with more than 500 model files lost versioning, polymorphic and the
|
|
223
|
+
other patterns that only showed up in a later file. (#585)
|
|
224
|
+
- **A booted STI base lists only its direct subclasses as its children.** A
|
|
225
|
+
grandchild was listed beside them, which disagreed with the static tier and
|
|
226
|
+
with the grandchild's own parent. (#531)
|
|
227
|
+
- **A file that does not compile leaves out only the files whose constants
|
|
228
|
+
resolve to it.** Names now resolve the way Ruby resolves them where they are
|
|
229
|
+
written, so a broken `Legacy::ApplicationRecord` no longer marks every
|
|
230
|
+
top-level model as unloadable, and a broken `Admin::ApplicationController` no
|
|
231
|
+
longer takes every top-level controller. Inside a namespace that defines its
|
|
232
|
+
own class of that name, a broken top-level one blocks nothing. (#578)
|
|
233
|
+
- **`rails_get_concern` names a concern that another concern includes, and the
|
|
234
|
+
classes that get it through that concern.** Before, a concern included only by
|
|
235
|
+
another concern read as "Nothing under app/ includes this concern." A module
|
|
236
|
+
passes its concern on the same way wherever its file sits, so the classes that
|
|
237
|
+
include it are listed too. Each includer is labelled by its own declaration,
|
|
238
|
+
`(concern)` or `(module)`, never by its folder, a module that wires a
|
|
239
|
+
validator is named by the module holding the `validates` call, and a plugin's
|
|
240
|
+
`concerns/group.rb` declaring `Billing::Group` is no longer taken for the
|
|
241
|
+
app's `Group`. A constant written inside `module A::B` resolves the way Ruby
|
|
242
|
+
resolves it there, never through `A`, for includers, a model's or controller's
|
|
243
|
+
concerns, a controller's base class and a spec's described class. A class
|
|
244
|
+
written `class ::Foo` inside a module is named Foo, for everything declared in
|
|
245
|
+
it, and a constant used in a `Class.new` block resolves from the scope around
|
|
246
|
+
the assignment. A class written `class Shop::Draft` inside `module Shop` is
|
|
247
|
+
Shop::Draft, as Ruby's lexical lookup finds the enclosing Shop, and
|
|
248
|
+
Shop::Shop::Draft below a `Shop = Class.new`: a constant written with `=`,
|
|
249
|
+
with `||=` where the name does not exist yet, or in a multiple assignment
|
|
250
|
+
counts as declared for every name read after it, in models, tests, Grape APIs
|
|
251
|
+
and mixin records alike. A module read out of another file finds its includes
|
|
252
|
+
through its own nesting, so `include Marks` in `module Shop::Tagging` inside
|
|
253
|
+
`module Shop`, in `module Foo::Tagging` inside `class ::Foo`, or in a
|
|
254
|
+
`Module.new` block reaches the module Ruby includes. A `Struct.new`,
|
|
255
|
+
`Module.new` or `Data.define` block owns its includes and declarations as a
|
|
256
|
+
`Class.new` block does. A receiver such as `Post.prepend(X)` inside
|
|
257
|
+
`Blog::Post` resolves through the nesting it is written in. A class assigned
|
|
258
|
+
as `Shop::Draft = Class.new(Base) do` inside `module Shop` is Shop::Draft, and
|
|
259
|
+
`class Billing::Invoice` inside another module is Billing::Invoice when the
|
|
260
|
+
file declares Billing above it, as Ruby's lexical lookup finds them. A model's
|
|
261
|
+
`self.table_name` is read off the class its written name resolves to the same
|
|
262
|
+
way, so `class Shop::Order` inside `module Shop` and `class ::Order` beside it
|
|
263
|
+
each keep their own. An `||=` and every segment of a compact path find a
|
|
264
|
+
constant the superclass declares when the file declares that superclass above
|
|
265
|
+
the class, as Ruby's lookup does, and so does a route constant read in a class
|
|
266
|
+
whose superclass writes it. `self::Sub =` inside Ledger is Ledger::Sub, and a
|
|
267
|
+
class opened in a `Class.new` block is named from the scope around the block,
|
|
268
|
+
as Ruby defines it. What a `Foo ||= Class.new(Base) do ... end` block declares
|
|
269
|
+
belongs to Foo when the `||=` assigns it, so
|
|
270
|
+
`Part ||= Class.new(ApplicationRecord) do` inside Widget is the model
|
|
271
|
+
Widget::Part with its associations; when Foo already exists the block never
|
|
272
|
+
runs, so nothing in it, a class opened in it included, is credited to
|
|
273
|
+
anything. (#514)
|
|
274
|
+
- **Static model associations match the model's reflections.** A name declared
|
|
275
|
+
twice is listed once, with the options of the declaration that runs last, and
|
|
276
|
+
the associations a concern adds sit where the concern is included. Across a
|
|
277
|
+
class and its bases they list as `reflect_on_all_associations` does on Rails
|
|
278
|
+
7.0 to 8.1: the outermost base's first, each where it last ran. A child's
|
|
279
|
+
association replaces a base's of the same name whatever the macro, and one a
|
|
280
|
+
base's class method declares wins over the class's own, at the place the class
|
|
281
|
+
calls that method, when the class calls it after declaring its own.
|
|
282
|
+
- **A file that declares a module as `X = Module.new do ... end` is named for
|
|
283
|
+
that module, as the `module X` form is.** An acronym the app registers names
|
|
284
|
+
it, and such a file under app/controllers or app/serializers is read as a
|
|
285
|
+
mixin, not as a controller or serializer class. (#514)
|
|
286
|
+
- **`rails_dependency_graph` gives a polymorphic belongs_to only the models
|
|
287
|
+
whose `as:` association points at its own model.** Two models with `belongs_to
|
|
288
|
+
:record, polymorphic: true` no longer share targets, and the false cycle that
|
|
289
|
+
came from it is gone. An `as:` pointed at an STI subclass or base reaches the
|
|
290
|
+
classes of that family above and below it. A polymorphic `as:` whose class
|
|
291
|
+
cannot be named adds its model to no belongs_to list. (#551)
|
|
292
|
+
- **A focused `rails_dependency_graph(model:)` costs about what the whole graph
|
|
293
|
+
does.** It finds the links into each model from one index built once, where it
|
|
294
|
+
used to rescan every model for every node. (#602)
|
|
295
|
+
- **`rails_onboard` names a payment model by a whole word of its name.**
|
|
296
|
+
Airplane, Explanation, FloorPlan and Discharge are no longer listed as payment
|
|
297
|
+
models; PaymentPlan, Billing::Invoice, Subscription and Plan still are. (#553)
|
|
298
|
+
- **Seeds list only the models the app has.** Plain Ruby classes built in a seed
|
|
299
|
+
file (`Date`, `Time`, `Random`, `Set`, a service object) are no longer
|
|
300
|
+
reported as seeded models; booted, a record class a gem defines, such as
|
|
301
|
+
`ActiveStorage::Blob`, still counts. (#570)
|
|
302
|
+
- **`rails_dependency_graph` with `show_sti` shows every level of an STI tree.**
|
|
303
|
+
A grandchild such as `SportsCar < Car < Vehicle` is now drawn under its own
|
|
304
|
+
parent, on both tiers, where before only the base's direct subclasses showed.
|
|
305
|
+
- **`rails_generate_test` writes no failing `validate_uniqueness_of` for a
|
|
306
|
+
uniqueness rule with `conditions:`.** A lambda option on a validation read
|
|
307
|
+
from reflection is now kept as `(proc)`, so the generator falls back to its
|
|
308
|
+
plain example, as it does for other options it cannot read.
|
|
309
|
+
- **A controller declared beside another in its file gets only its own filters
|
|
310
|
+
and strong params.** The booted tier read skips and filter options from the
|
|
311
|
+
whole file, and both tiers read strong params from it, so one class showed the
|
|
312
|
+
other's; each is now read from its own class body. A booted job in such a file
|
|
313
|
+
also shows its own `queue_as` block source.
|
|
314
|
+
- **performance_check keeps a controller's N+1 risks when one of its views
|
|
315
|
+
renders records whose `to_partial_path` is computed.** The scan used to crash
|
|
316
|
+
on that render and drop every risk in the controller, without a word. (#573)
|
|
317
|
+
- **performance_check rates an N+1 as preloaded only when a preload call names
|
|
318
|
+
that association at its top level.** A `where(user: ...)`, a `joins(:user)` or
|
|
319
|
+
a nested `includes(comments: :user)` no longer turns a missing
|
|
320
|
+
`includes(:user)` into "no action needed". A preload written without
|
|
321
|
+
parentheses (`includes :user`), in an action or a scope, is read as one.
|
|
322
|
+
(#574)
|
|
323
|
+
- **The N+1 scan no longer slows down with actions times views.** It builds its
|
|
324
|
+
template and partial lookups once, so 2,400 actions over 2,700 views take
|
|
325
|
+
about 5s where they took 20s. (#517)
|
|
326
|
+
- **performance_check reads every model the app loads, whatever its superclass
|
|
327
|
+
is written as.** A subclass of a second abstract base, an STI child or a class
|
|
328
|
+
under `ActiveRecord::Base` now gets the missing counter_cache, Model.all and
|
|
329
|
+
eager-load checks. (#568)
|
|
330
|
+
- **The GraphQL resolver list names only resolver classes.** An app that keeps
|
|
331
|
+
its resolvers in `app/graphql/resolvers` no longer gets a fake `Resolvers`
|
|
332
|
+
entry for the folder. (#588)
|
|
333
|
+
- **API versioning lists only `api/v<number>` folders.** A resource namespace
|
|
334
|
+
such as `api/vendors` or `api/videos` is no longer reported as an API version.
|
|
335
|
+
(#569)
|
|
336
|
+
- **`get_controllers` action detail lists every private method the action
|
|
337
|
+
calls.** One passed as an argument (`paginate(scope)`) or with a call chained
|
|
338
|
+
on it (`visible_posts.size`) now shows with its source, and a name that only
|
|
339
|
+
appears in a comment no longer does. (#586)
|
|
340
|
+
- **A controller is an API controller only when its chain reaches
|
|
341
|
+
`ActionController::API`.** Without booting, a parent named `APIController` or
|
|
342
|
+
`API::BaseController` under `ApplicationController` was read as an API
|
|
343
|
+
controller, so it lost its layout and got JSON tests. It now keeps its layout
|
|
344
|
+
and gets HTML tests. A child of an API-only `ApplicationController` is now
|
|
345
|
+
marked as an API controller. (#539)
|
|
346
|
+
- **The security section no longer says CSRF protection is on in an API-only
|
|
347
|
+
app.** When `ApplicationController` inherits `ActionController::API`, which
|
|
348
|
+
leaves out `RequestForgeryProtection`, `csrf` reports it as not included. An
|
|
349
|
+
API app that includes `RequestForgeryProtection` back reports it as enabled
|
|
350
|
+
only when `protect_from_forgery` turns it on, and as off when it never does.
|
|
351
|
+
The answer says CSRF is off when `verify_authenticity_token` does not run,
|
|
352
|
+
names a `skip_before_action :verify_authenticity_token` after protection as
|
|
353
|
+
skipped, and, when ApplicationController cannot load, reads
|
|
354
|
+
`default_protect_from_forgery` and the file's own protect and skip calls.
|
|
355
|
+
(#572)
|
|
356
|
+
- **A controller or job file that declares a second controller or job lists both
|
|
357
|
+
without booting.** Each class is read from its own body, so one no longer
|
|
358
|
+
takes the other's actions, filters or queue, an error class nested in a worker
|
|
359
|
+
is not a worker, and a second controller routes by its own name. A class
|
|
360
|
+
declared beside a job is listed only when its own chain reaches a job or
|
|
361
|
+
worker base, and a controller declared in a file named for another routes by
|
|
362
|
+
its own class name, as Rails' `controller_path` does. (#513)
|
|
363
|
+
- **Views list the templates of a folder named layouts anywhere but
|
|
364
|
+
app/views/layouts.** An admin/layouts controller and an app checked out under
|
|
365
|
+
a folder named layouts kept no templates, because the check read the path on
|
|
366
|
+
disk. (#575)
|
|
367
|
+
- **A layout in a subfolder of app/views/layouts is listed, by its path under
|
|
368
|
+
layouts/.** The views answer and `get_view controller:"layouts"` read only the
|
|
369
|
+
top level, so `layouts/admin/application` and its slots were missing, and a
|
|
370
|
+
nested file of the same name would have merged with the top-level layout.
|
|
371
|
+
get_turbo_map finds the morph meta tag in a layout in a subfolder of
|
|
372
|
+
`layouts/`. (#576)
|
|
373
|
+
- **get_view shows the locals each partial is rendered with.** Both `render
|
|
374
|
+
"posts/form", post: @post` and `render partial: "posts/form", locals: { ... }`
|
|
375
|
+
are read, from templates and from other partials, and only from calls that
|
|
376
|
+
name that partial. `rails_get_partial_interface` reads locals by the same
|
|
377
|
+
rule: a call split over lines keeps every key, a nested hash's keys are not
|
|
378
|
+
listed, and `render "x", locals: {...}` is one local named `locals`, as Rails
|
|
379
|
+
passes it. `rails_get_partial_interface` reads `json.partial!` in hash form
|
|
380
|
+
the way jbuilder does (every key beside `partial:`, `as:`, `collection:` and
|
|
381
|
+
`cached:` is a local unless `locals:` is given); a partial rendered with
|
|
382
|
+
`collection:` or `object:` lists the record local Rails gives it (the `as:`
|
|
383
|
+
name or the partial's own, plus `_counter` and `_iteration` for a collection);
|
|
384
|
+
and a bare name Rails finds under `application/` reaches that partial in both
|
|
385
|
+
tools. (#525)
|
|
386
|
+
- **get_view returns a view named by path exactly as written.** Inline SVGs and
|
|
387
|
+
repeated class strings were rewritten in the text an agent edits from. The
|
|
388
|
+
full layouts listing shows each layout as written, and a controller's full
|
|
389
|
+
listing says when it shortened icons and repeated classes. (#511)
|
|
390
|
+
- **The generated guide names a partial in app/views/application as
|
|
391
|
+
`application/<name>`.** It printed a `shared/` path that does not exist.
|
|
392
|
+
(#536)
|
|
393
|
+
- **get_turbo_map reads a parenthesized `turbo_frame_tag(:comments)` or
|
|
394
|
+
`turbo_stream_from(:notifications)` call.** The frame id and stream name used
|
|
395
|
+
to come out as `(dynamic)`, and the map warned about a stream that does not
|
|
396
|
+
exist. Only `turbo_stream_from` itself counts, so `my_turbo_stream_from` is no
|
|
397
|
+
phantom subscription, and `turbo_stream_from (:a), :b` lists both streams.
|
|
398
|
+
(#577)
|
|
399
|
+
- **rails_get_stimulus writes the value and class attributes Stimulus reads for
|
|
400
|
+
names with an acronym or a digit.** `apiURL` is `data-x-api-u-r-l-value` and
|
|
401
|
+
`step2Count` is `data-x-step2-count-value`, from Stimulus's own `dasherize`.
|
|
402
|
+
(#560)
|
|
403
|
+
- **partial_interface credits a local only with calls made on the local
|
|
404
|
+
itself.** `@user.admin?` and `post.user.avatar` no longer show up as calls on
|
|
405
|
+
a `user` local. (#563)
|
|
406
|
+
- **helper_methods counts a view as using a helper only where it calls the
|
|
407
|
+
helper by name.** `@post.subtitle`, the CSS class `icon-edit` and `helper_m10`
|
|
408
|
+
no longer count, and in an ERB view neither do plain markup and strings. A
|
|
409
|
+
framework helper is named only where a view calls it by its own name, so
|
|
410
|
+
`@current_user_id` and `current_user_path` no longer list Devise's
|
|
411
|
+
`current_user`. A HAML or Slim view is read the way haml and slim parse it, so
|
|
412
|
+
a tag such as `%title`, page text such as `follow @shop`, and comments name no
|
|
413
|
+
helper and no ivar. Attribute lists, object refs, continued lines, `:ruby`
|
|
414
|
+
filters and `#{}` in text still count, and a quote inside `#{...}` no longer
|
|
415
|
+
hides the Ruby after it. Ruby strings in a view are read the way Ruby reads
|
|
416
|
+
them, so `"by #@author"` counts `@author` and an escaped `"\#{@x}"` counts
|
|
417
|
+
nothing. (#562)
|
|
418
|
+
- **helper_methods `detail:"full"` takes time in step with the views' total size
|
|
419
|
+
instead of its square.** On 2,000 views (16 MB) it runs about three times
|
|
420
|
+
faster. Each view is read once, and files no longer join into one name across
|
|
421
|
+
their boundary. (#596)
|
|
422
|
+
- **component_catalog lists a prop's allowed values when its constant table ends
|
|
423
|
+
in `.freeze`.** (#581)
|
|
424
|
+
- **view and context schema hints no longer warn about a missing model for an
|
|
425
|
+
ivar that holds no model.** `@stats` and `@chart_data` used to warn about
|
|
426
|
+
`Stat` and `ChartDatum` models that never existed. (#580)
|
|
427
|
+
- **`rails_security_scan` and `rails_validate` read `./app/x.rb` (and
|
|
428
|
+
`././app/x.rb`) as `app/x.rb` and a folder name as that folder only.** A `./`
|
|
429
|
+
path used to be reported clean and `app/controllers/admin` picked up
|
|
430
|
+
`admin_controller.rb`. (#522)
|
|
431
|
+
- **`rails_review_changes` shows a file's staged and unstaged changes together,
|
|
432
|
+
as `git diff HEAD` does.** A staged validation removal is still reported after
|
|
433
|
+
a later edit to the same file. (#523)
|
|
434
|
+
- **`rails_review_changes` shows a namespaced controller's own routes and names
|
|
435
|
+
a namespaced model with its namespace.**
|
|
436
|
+
`app/controllers/admin/posts_controller.rb` no longer gets the top-level
|
|
437
|
+
`PostsController`'s routes. (#557)
|
|
438
|
+
- **`rails_review_changes` counts a foreign key as indexed only by an
|
|
439
|
+
`add_index` on its own table that leads with it, or with its polymorphic
|
|
440
|
+
`[type, id]` pair.** An index on another table or column, or one that names
|
|
441
|
+
the key only in its `where:`, no longer hides a missing index, and one
|
|
442
|
+
`index: false` flags only its own reference. (#558)
|
|
443
|
+
- **`rails_review_changes` warns when a change removes a `validates_presence_of`
|
|
444
|
+
line or any other `validates_*` macro.** A line rewritten as `validates :name,
|
|
445
|
+
...` still reads as kept. A method named `validate_email` is no longer read as
|
|
446
|
+
a validation. (#559)
|
|
447
|
+
- **`rails_validate` asks for no migration for a validated attribute the model
|
|
448
|
+
defines as a method, and finds a callback method a parent class defines on the
|
|
449
|
+
static tier.** A parent outside the app's files is no longer reported as
|
|
450
|
+
missing the method. (#509)
|
|
451
|
+
- **`rails_validate` warns about `Model.all` in the controller
|
|
452
|
+
`rails_performance_check` found it in.** It matches the full path, so
|
|
453
|
+
`admin/posts_controller.rb` does not take `posts_controller.rb`'s finding.
|
|
454
|
+
(#594)
|
|
455
|
+
- **`rails_get_edit_context` takes the method that actually holds the match.** A
|
|
456
|
+
line below a method that already ended keeps its window and names only the
|
|
457
|
+
class, and an endless method no longer takes in the next one. (#524)
|
|
458
|
+
- **`rails_validate` checks the instance variables of views under a folder named
|
|
459
|
+
`layouts`, such as `app/views/admin/layouts/`.** Only `app/views/layouts/` is
|
|
460
|
+
skipped, since that is where the app's layouts are.
|
|
461
|
+
- **`rails_get_test_info` with `detail: "full"` lists each factory's attributes
|
|
462
|
+
and traits.** It looked for the file under a doubled path, so it never found
|
|
463
|
+
one and printed only the factory names. Each factory file's traits stay under
|
|
464
|
+
its own path, so two files named `users.rb` in different folders no longer
|
|
465
|
+
lose one's traits; the JSON context's `factory_traits` are keyed by that path,
|
|
466
|
+
as `factory_names` already were. Each factory's attributes are read as
|
|
467
|
+
FactoryBot declares them, `do ... end` blocks, `association`, `sequence` and
|
|
468
|
+
bare names included, with transient ones marked, callbacks left out, and each
|
|
469
|
+
trait's and child factory's attributes listed under it. (#561)
|
|
470
|
+
- **`rails_generate_test` and `rails_get_test_info` no longer give
|
|
471
|
+
`UsersController` the spec of `UserController`.** The app's learned test
|
|
472
|
+
layout tried the singular name for controllers too. (#555)
|
|
473
|
+
- **`rails_generate_test` writes a Minitest file that parses when a model
|
|
474
|
+
declares an association with a computed name.** That association is left out,
|
|
475
|
+
as the RSpec output already did. (#554)
|
|
476
|
+
- **`rails_generate_test` writes a Minitest `ORDER BY` assert only for a scope
|
|
477
|
+
that sorts.** A scope that joins `orders` or filters a `bordered` column no
|
|
478
|
+
longer gets a test that fails on its first run. (#556)
|
|
479
|
+
- **`rails_generate_test` writes shoulda matchers that pass for an optional
|
|
480
|
+
`belongs_to`, a scoped or case-insensitive uniqueness, and an enum with
|
|
481
|
+
explicit values, string values, a prefix, or `scopes: false` or
|
|
482
|
+
`instance_methods: false`.** Each matcher now carries the qualifier the
|
|
483
|
+
declaration needs. Where an option can't be read or told to the matcher, it
|
|
484
|
+
falls back to the plain example: a reflection check for the association, the
|
|
485
|
+
validation's kind for the uniqueness, the values for the enum. The hash form
|
|
486
|
+
(`enum status: {...}, _instance_methods: false`) reads that key as the option,
|
|
487
|
+
not as a second enum, and on Rails 7.0, whose enum ignores
|
|
488
|
+
`instance_methods:`, the enum keeps its value methods. (#510)
|
|
489
|
+
- **`rails_analyze_feature` lists a service's or job's ENV names only when its
|
|
490
|
+
path under `app/services` or `app/jobs` holds the feature word.** A folder
|
|
491
|
+
above the app (`Users`, the app's own name) matched every file. (#549)
|
|
492
|
+
- **`rails_analyze_feature` says a model, controller, job or service has no test
|
|
493
|
+
unless a test is named for it or describes it.** Before, any test whose path
|
|
494
|
+
merely contained the name hid the gap, such as `postmark_event_spec.rb` for
|
|
495
|
+
Post or `user_mailer_spec.rb` for User. `rails_review_changes` reads the same
|
|
496
|
+
rule, so a controller has no test changes unless a changed test is named for
|
|
497
|
+
it or describes it, and the `posts_routing_spec.rb` and
|
|
498
|
+
`posts_request_spec.rb` rspec-rails writes count for `PostsController`. Those
|
|
499
|
+
names count only in a controller test folder, and a controller's name is never
|
|
500
|
+
singularized, so `spec/models/post_spec.rb` is no test of `PostsController`
|
|
501
|
+
and `user_controller_spec.rb` none of `UsersController`. A test under a
|
|
502
|
+
namespace folder covers only the subject in that namespace:
|
|
503
|
+
`spec/controllers/admin/posts_controller_spec.rb` no longer counts for a
|
|
504
|
+
top-level `PostsController`. A model spec named `widget_model_spec.rb` counts
|
|
505
|
+
for `Widget`, the suffix `rails_get_test_info` already looks up. The tests an
|
|
506
|
+
in-repo engine or pack keeps in its own `spec/` or `test/` count for that
|
|
507
|
+
component's classes only. A test filed anywhere else counts when any of its
|
|
508
|
+
example groups (`describe`, `context`, `feature`, nested or not) names the
|
|
509
|
+
subject's declared constant, resolved from the modules around it as Ruby
|
|
510
|
+
resolves it, or its minitest class does without `Test`, so
|
|
511
|
+
`spec/jobs/sweep_spec.rb` describing `Jobs::Sweep` covers
|
|
512
|
+
`app/jobs/scheduled/sweep.rb`. A test filed under a subject's name counts
|
|
513
|
+
unless its top-level example groups or test class describe another class the
|
|
514
|
+
app declares, and a controller test outside `controllers/` and `functional/`
|
|
515
|
+
(request, routing, integration, system), or a minitest
|
|
516
|
+
`ActionDispatch::IntegrationTest` anywhere, counts for the controller it is
|
|
517
|
+
filed as whatever it describes, since it reaches that controller through the
|
|
518
|
+
routes. (#552)
|
|
519
|
+
- **`rails_get_context` with `feature:` adds the schema of only the models its
|
|
520
|
+
feature word names as whole words.** `post` no longer brings in
|
|
521
|
+
`PostmarkEvent` or controllers that merely start with the word. (#550)
|
|
522
|
+
- **Running the install again keeps an `ai_tools`, `tool_mode` or
|
|
523
|
+
`context_files` line it does not rewrite, and says so.** An array spread over
|
|
524
|
+
several lines got its first line replaced and the initializer stopped parsing.
|
|
525
|
+
(#504)
|
|
526
|
+
- **Context files, MCP configs and the selection record write through a symlink
|
|
527
|
+
to the file it names, and keep that file's mode.** A link that leads out of
|
|
528
|
+
the app, or nowhere, is left as it is and reported. Before, the write replaced
|
|
529
|
+
the link with a copy and reset the mode. A write into a directory a symlink
|
|
530
|
+
carries out of the app is refused too. Removing the gem's MCP entry through a
|
|
531
|
+
symlinked config takes it out of the file the link names and keeps the link,
|
|
532
|
+
and nothing a symlink carries out of the app is deleted. A fetched guide in
|
|
533
|
+
`rails_search_docs` is still answered when `tmp/` is a symlink out of the app;
|
|
534
|
+
it is not cached there, and the answer names no path. Uninstall and the
|
|
535
|
+
dropped-tool cleanup take the gem's block out of a root file that is a symlink
|
|
536
|
+
into the app (CLAUDE.md -> AGENTS.md), and a context file under a folder
|
|
537
|
+
symlink that leads nowhere is reported as left instead of stopping the run.
|
|
538
|
+
(#505)
|
|
539
|
+
- **Removing the Codex entry leaves the rest of `.codex/config.toml` as it
|
|
540
|
+
was.** Blank lines inside a `"""` or `'''` string were squeezed, which changed
|
|
541
|
+
the value Codex reads. Your blank lines around the removed table are kept;
|
|
542
|
+
only the one the cut leaves is dropped. (#519)
|
|
543
|
+
- **A context run keeps an MCP entry whose command the gem did not write, such
|
|
544
|
+
as SETUP.md's Docker entry, and says so.** When it rewrites its own entry it
|
|
545
|
+
merges into it, so keys and env vars it never writes (`cwd`,
|
|
546
|
+
`startup_timeout_sec`, `RAILS_ENV`) stay, and so do comments in the gem's
|
|
547
|
+
Codex table and its env table. A Codex env var set as a dotted key in the
|
|
548
|
+
gem's table survives, and a workspace write keeps a bare `rails-ai-context`
|
|
549
|
+
entry that starts the server through Docker, naming it in its note. A Codex
|
|
550
|
+
env var whose name needs quotes keeps them, and a line you typed in the gem's
|
|
551
|
+
env table stays. (#506)
|
|
552
|
+
- **`.claude/rules/rails-models.md` lists the first 30 models and counts the
|
|
553
|
+
rest**, as the other models rules do. On a large app it grew to about 190 KB
|
|
554
|
+
and loaded on every model edit. (#530)
|
|
555
|
+
- **`database_stats` and the runtime table sizes leave out `schema_migrations`
|
|
556
|
+
and `ar_internal_metadata` on PostgreSQL and MySQL too.** Every database
|
|
557
|
+
family now applies the schema's own rule, including the app's table prefix and
|
|
558
|
+
suffix, so the counts name only the tables the schema lists.
|
|
559
|
+
- **A static run of an app with no `config/routes.rb` answers no routes, as the
|
|
560
|
+
booted tier does.** Doctor no longer lists `routes` as a failing introspector
|
|
561
|
+
there, and the routes section says there was no route file to parse.
|
|
562
|
+
- **`facts` lists the tables of every database in `database.yml`.** A second
|
|
563
|
+
database's tables were missing from the list and the count. Each one now reads
|
|
564
|
+
`- dogs (animals; 2 cols, 0 indexes, 0 FKs)`. (#527)
|
|
565
|
+
- **The `facts` title reads `Rails App` for an app whose Application class has
|
|
566
|
+
no module.** It used to read `# - Schema Facts`. (#604)
|
|
567
|
+
- **`rails 'ai:tool[NAME]'` accepts a trailing `JSON=1` or `RAILS_ENV=test`, the
|
|
568
|
+
form `docs/GUIDE.md` shows.** The task passed them to the tool as params, so
|
|
569
|
+
it failed. (#534)
|
|
570
|
+
- **`session_context` status keeps one row per call when a param holds `|` or a
|
|
571
|
+
line break.** (#600)
|
|
572
|
+
- **`tool search_code --pattern -h` searches for `-h`.** `-h` and `-j` right
|
|
573
|
+
after a flag that takes a value are read as that value. After a boolean flag
|
|
574
|
+
they are still help and JSON. `tool NAME --json -h` prints the tool's help
|
|
575
|
+
without booting the app. (#535)
|
|
576
|
+
- **`tool --json` and `JSON=1` answer every refusal with the envelope on stdout,
|
|
577
|
+
at exit 1.** An unknown tool, unknown param, missing value or failed boot
|
|
578
|
+
printed nothing on stdout. Now `"error": true` carries the message, "did you
|
|
579
|
+
mean" included. A call whose `-j` is left for the tool answers a failed boot
|
|
580
|
+
with the envelope too. A run that stops before the tool starts says "The tool
|
|
581
|
+
did not run: rails-ai-context stopped before it could. stderr says why." from
|
|
582
|
+
the binary and from `rails 'ai:tool[NAME]'` alike. (#590)
|
|
583
|
+
- **The standalone server's bind warning describes its own Host check on every
|
|
584
|
+
mcp SDK.** Below mcp 0.23 it said every tool answers anyone, but the server
|
|
585
|
+
refuses a non-loopback Host there too. (#599)
|
|
586
|
+
- **A cached answer goes stale when a root file a reader reads changes, and when
|
|
587
|
+
a file is renamed.** The fingerprint now covers the Dockerfile, Procfile,
|
|
588
|
+
compose, CI and deploy configs, `.env` examples, JS lockfiles, framework and
|
|
589
|
+
bundler configs, the manifests in a declared `frontend_paths` folder (inside
|
|
590
|
+
the app or outside it) and in the JS workspace root above the app, Ruby
|
|
591
|
+
version files, `bin/` scripts and config `.yaml`, each taken from the list its
|
|
592
|
+
reader reads, and hashes each file's path with its mtime. `watch` and live
|
|
593
|
+
reload also watch `bin/`, `.github/workflows` and the other folders those
|
|
594
|
+
files sit in. A `.github/workflows` folder that is itself a symlink is never
|
|
595
|
+
walked or watched. (#515)
|
|
596
|
+
- **An app whose folder path has brackets, braces, a star or a question mark
|
|
597
|
+
gets the same answers as one under a plain folder.** The fingerprint walk,
|
|
598
|
+
doctor and every introspector globbed with the app root inside the pattern, so
|
|
599
|
+
`/Users/me/work [old]/shop` found no models, jobs, helpers, rake tasks, packs,
|
|
600
|
+
locales, environments, credentials, mailer previews, migrations or test files,
|
|
601
|
+
and watch and live reload never saw a change. Every glob now matches below the
|
|
602
|
+
root and reads the root as a path; patterns the app configures still glob as
|
|
603
|
+
before. (#532)
|
|
604
|
+
- **`watch --no-boot` picks up a folder added under `app/` while it runs.** Each
|
|
605
|
+
rewrite drops the cached folder lists, as a server does, so the new folder's
|
|
606
|
+
models reach the context files. (#533)
|
|
607
|
+
- **The env section names a service on a `.co.uk`, `.com.au`, `.co.nz` or
|
|
608
|
+
`.or.jp` host by its own label.** Such services were shown as "Co" or "Com",
|
|
609
|
+
and two in one file merged into one row. (#566)
|
|
610
|
+
- **A resource read answers from the shared context the tools read.** Each read
|
|
611
|
+
ran a full introspection outside the shared cache; it now costs what a tool
|
|
612
|
+
call costs, and concurrent reads introspect once. (#516)
|
|
613
|
+
- **Percentages print the exact share.** 29 of 100 i18n keys showed 28.9%
|
|
614
|
+
because the floor ran on a float a hair under 29; the math is now exact.
|
|
615
|
+
(#591)
|
|
616
|
+
- **Gemfile groups match Bundler.** A gem's `groups:` option is read, and
|
|
617
|
+
`group:` inside a `group` block adds to the block's groups instead of
|
|
618
|
+
replacing them. (#582)
|
|
619
|
+
- **The context file's env section lists every name rails_get_config lists.**
|
|
620
|
+
Lowercase, one-letter and underscore-first ENV names are no longer dropped.
|
|
621
|
+
(#567)
|
|
622
|
+
- **A lambda in `config.log_tags` reads as computed at request time.** The
|
|
623
|
+
active_support and observability sections no longer print a Proc address and
|
|
624
|
+
an absolute path that change on every boot. (#584)
|
|
625
|
+
- **The frontend framework label keeps a version range as written.** `^18.2.0 ||
|
|
626
|
+
^19.0.0` read as "React 18.2.019.0.0"; a single requirement still loses its
|
|
627
|
+
leading operator. (#564)
|
|
628
|
+
- **The credentials section lists key names under the file Rails read.** With
|
|
629
|
+
`config/credentials/<env>.yml.enc` present, its keys are no longer shown under
|
|
630
|
+
`config/credentials.yml.enc`, and `master_key_source` names the key Rails
|
|
631
|
+
uses, or `missing`; a static run picks that key by the locked Rails version's
|
|
632
|
+
rule. A credentials file that is a symlink counts as the file it stands in
|
|
633
|
+
for. Credentials at a custom `config.credentials.content_path` get their own
|
|
634
|
+
`custom` entry with the key names, and a path outside the app root shows as
|
|
635
|
+
`(outside the app root)`. (#565)
|
|
636
|
+
- **A static run reports the credentials files it can see.** The `default` entry
|
|
637
|
+
no longer comes back as `error: "inspection failed"` without a boot. It lists
|
|
638
|
+
the file and key-file presence, and no key names, since nothing is decrypted
|
|
639
|
+
without a boot.
|
|
640
|
+
- **A static run reports `require_master_key` as the app sets it.** Without a boot
|
|
641
|
+
the credentials section said `false` whatever the config said; it now reads a
|
|
642
|
+
literal `config.require_master_key = true/false` from `config/application.rb`,
|
|
643
|
+
the environment file or an initializer, following a `require_relative` into
|
|
644
|
+
`config/` the way Ruby runs it, and answers Rails' default, `false`, when none
|
|
645
|
+
of them sets it. A value it cannot read (an expression, a condition, a `load`
|
|
646
|
+
or a required file outside `config/`) reports as unavailable. Only a call, or a
|
|
647
|
+
send that names the setting, counts as a mention, so the name in a string or
|
|
648
|
+
after `__END__` leaves the answer at Rails' default.
|
|
649
|
+
- **`rails_analyze_feature` names each related model the class Rails would
|
|
650
|
+
load.** In the static tier a through association reads its class from its
|
|
651
|
+
source, and a singular association keeps its name singular, so
|
|
652
|
+
`belongs_to :preferences` is `Preferences` and
|
|
653
|
+
`has_many :followers, through: :subscriptions, source: :user` is `User`. The
|
|
654
|
+
dependency graph, the N+1 scan, the turbo map and the foreign key index check
|
|
655
|
+
use the same rule, and rendered records read a through association's class off
|
|
656
|
+
its source on the hop's model, wherever an autoload root keeps that model, a
|
|
657
|
+
pack or an engine included. The receiver a view renders through counts as a
|
|
658
|
+
model only when the model directories hold its file, so `render @cart.items`
|
|
659
|
+
with a `Cart` service still renders items. A hop or source counts when the
|
|
660
|
+
model's file, its included concerns or its parent models declare it, with its
|
|
661
|
+
`class_name`, a parent being read by the constant its superclass names; one
|
|
662
|
+
the app's source does not declare names no model. A `class_name` wins over
|
|
663
|
+
`source_type`, as Rails reads it, here and in the dependency graph. A
|
|
664
|
+
polymorphic `belongs_to` names no related model there, in the N+1 scan or in
|
|
665
|
+
the turbo map, since Rails reads its class from each row; the dependency graph
|
|
666
|
+
still draws it as a polymorphic interface.
|
|
667
|
+
- **A rendered record whose model constant only a Zeitwerk `inflect` override
|
|
668
|
+
spells keeps its parent's associations and its own `to_partial_path`.**
|
|
669
|
+
`render @html_page.links`, a through association across such a model and
|
|
670
|
+
`render @html_pages` name the partial Rails renders, in the view,
|
|
671
|
+
partial-interface and N+1 answers.
|
|
672
|
+
- **A view's "renders:" list names the partial Rails renders for a bare
|
|
673
|
+
`render @records`.** It reads the model's own `to_partial_path` and the view's
|
|
674
|
+
controller namespace, the way partial_interface does, so `render @comments` in
|
|
675
|
+
an admin view lists `admin/comments/comment`, and a has_many with a
|
|
676
|
+
`class_name` lists that class's partial. It used to list the plural model
|
|
677
|
+
name. The records' model comes from the names the view writes
|
|
678
|
+
(`@post.comments`, `@comments`), read from the model directories only, so a
|
|
679
|
+
service or presenter under the view's namespace is never taken for the
|
|
680
|
+
records. A lone name with no model file falls back to its default partial,
|
|
681
|
+
prefixed with the controller namespace, when that partial exists in any view
|
|
682
|
+
root; otherwise a local keeps its name and an ivar names none, since Rails
|
|
683
|
+
never looks a partial up by the ivar's name. A form helper's block parameter,
|
|
684
|
+
whatever it is called and in a `do` or a brace block, renders the partial its
|
|
685
|
+
builder's `to_partial_path` names (`form` for FormBuilder), and a builder's
|
|
686
|
+
own `fields` and `fields_for` keep its builder class, as
|
|
687
|
+
`FormBuilder#fields_for` passes it on, while a builder handed to a partial
|
|
688
|
+
as a local reads as FormBuilder's and a local assigned a builder reads as that
|
|
689
|
+
builder. Which locals a template binds is read from its Ruby the way Ruby
|
|
690
|
+
binds them, so a `<script>`, an HTML or Slim/HAML attribute or a string under
|
|
691
|
+
the same name never turns a builder's `fields` block into a record's, nor, in
|
|
692
|
+
ERB and Phlex, another block's parameter; the builder is read only off a real
|
|
693
|
+
helper call and the block that helper takes, so prose naming fields, a
|
|
694
|
+
`fields:` key, a `%i[fields]` symbol before a loop, a record's own `fields`
|
|
695
|
+
called on an ivar, a constant, a chain or a local the view assigns or a plain
|
|
696
|
+
block binds (`post = Post.new`, `[Post.new].each do |post|`), or iterated
|
|
697
|
+
(`@post.fields(:visible).each do |field|`), or a brace block inside the
|
|
698
|
+
helper's arguments changes nothing. This holds in the "renders:" list and in
|
|
699
|
+
partial_interface, which reads builders from the same comment-stripped markup
|
|
700
|
+
as the view scan and finds a bare `render form` at the form partial beside the
|
|
701
|
+
view, as get_view does.
|
|
702
|
+
- **partial_interface finds a record's default partial kept in another view
|
|
703
|
+
root.** It reads the same partials set the view scan reads, built once a call,
|
|
704
|
+
so a partial in a root added with `config.paths["app/views"] <<` is credited
|
|
705
|
+
to the views that render it.
|
|
706
|
+
- **performance_check follows the partial Rails renders for `render @records` in
|
|
707
|
+
a namespaced controller.** The controller's namespace prefixes the record's
|
|
708
|
+
partial whether or not that file exists, and
|
|
709
|
+
`prefix_partial_path_with_controller_namespace = false` turns the prefix off,
|
|
710
|
+
the rule views and partial_interface use.
|
|
711
|
+
- **get_concern finds a concern nested in its namespace's file.**
|
|
712
|
+
`module Shop; module Tagging; extend ActiveSupport::Concern` kept in `shop.rb`
|
|
713
|
+
is looked up as `Shop::Tagging`, as Zeitwerk loads it with `Shop`, and the
|
|
714
|
+
concern listing and rails_get_active_support name it instead of the namespace
|
|
715
|
+
that only holds it. Each concern a file holds is read from its own module:
|
|
716
|
+
rails_get_concern and rails_get_active_support read its macros, callbacks and
|
|
717
|
+
`included` blocks from its own body, leaving out the modules it nests, so a
|
|
718
|
+
concern no longer shows a nested concern's associations or counts its
|
|
719
|
+
`included` block, and rails_get_concern with a namespace's name answers not
|
|
720
|
+
found and suggests the concerns it holds. A file whose module holds only
|
|
721
|
+
constant writes and modules is a namespace too,
|
|
722
|
+
`Error = Class.new(StandardError)` included, a concern defined beside a file's
|
|
723
|
+
class is listed and answered by name while the class is not, and a module
|
|
724
|
+
nested in a concern is listed only when it extends ActiveSupport::Concern
|
|
725
|
+
itself, whether it is written as a module, with `self::` or with an assigning
|
|
726
|
+
`||=`. A not-found answer from any tool suggests the nearest spellings and a
|
|
727
|
+
namespace's members together, five at most.
|
|
728
|
+
- **A concern's associations, filters, devise modules, actions and strong params
|
|
729
|
+
leave out the modules it nests, a `Module.new` one included, unless the
|
|
730
|
+
concern includes or prepends that module itself.** Ruby runs a nested module's
|
|
731
|
+
body for that module alone, so the model and controller tools no longer credit
|
|
732
|
+
a nested concern's `included` block or macros to the outer concern's
|
|
733
|
+
includers. A module the concern includes or prepends is the constant Ruby's
|
|
734
|
+
lookup reaches first, for every tool that reads it (associations, callbacks,
|
|
735
|
+
validations, scopes, filters, layouts, actions, devise modules and strong
|
|
736
|
+
params): one declared in the file that names it, or nested in its outer
|
|
737
|
+
constant's file, comes before a top-level module of the same name, unless that
|
|
738
|
+
file declares it below an include that runs as the file loads. A top-level
|
|
739
|
+
module the concern includes is followed too, through three levels of
|
|
740
|
+
includes, for its actions, devise modules and strong params.
|
|
741
|
+
- **A class's own include finds a module nested in one it included earlier.**
|
|
742
|
+
After `include Holdy`, a later `include Taggable` in the class body reads
|
|
743
|
+
`Holdy::Taggable` before the top-level `Taggable`, as Ruby looks constants up
|
|
744
|
+
through the class's ancestors, and a module it prepended comes before the ones
|
|
745
|
+
it includes after it. A mixin written from the top level (`include ::Taggable`)
|
|
746
|
+
reads the top-level module only, in the class body, a concern's `included`
|
|
747
|
+
block and a hook's `base.include`. A module only the superclass includes is
|
|
748
|
+
not read this way yet.
|
|
749
|
+
- **Static routes draw a block over a literal list once per element, as Rails
|
|
750
|
+
runs it.** `%w[groups g].each do |root_path|` listed one set of routes under
|
|
751
|
+
`/[INFERRED]/...`. It now lists `/groups/...` and `/g/...`, each with the name
|
|
752
|
+
Rails gives it, and a name an earlier pass took stays unnamed. A route inside
|
|
753
|
+
a block over a list the source does not spell out counts as a construct the
|
|
754
|
+
static tier could not expand. A `draw` inside the block follows each element's
|
|
755
|
+
file, and a bound element reads as a literal in `only:` and `except:`. A
|
|
756
|
+
concern defined in the block keeps its element wherever it is applied, and a
|
|
757
|
+
block that assigns or changes its own variable is drawn once with that
|
|
758
|
+
variable unknown, so its routes count as dynamic constructs; a call in it to a
|
|
759
|
+
method the routes file defines counts too, so the table never reads as
|
|
760
|
+
complete. The same goes for a block that hands its string to a pattern match
|
|
761
|
+
(`x => y`) or an attribute setter. Inside a block Ruby runs more than once
|
|
762
|
+
that the static tier does not unroll, such as `2.times`, `LOC.each_key` or an
|
|
763
|
+
`each` over a list it cannot read, every routing call and every call to a
|
|
764
|
+
method the routes file defines counts as such a construct, since Rails draws
|
|
765
|
+
them once per pass, while a mount or a verb route to a Rack app in such a
|
|
766
|
+
block is listed once as a mounted app; a `tap`, `then` or `yield_self` block
|
|
767
|
+
still draws in place, the calls it makes on the mapper it takes as its
|
|
768
|
+
parameter (`tap { |m| m.get "x" }`, an optional `|m = 1|` too) included, and
|
|
769
|
+
so does a `tap` or `yield_self` with no receiver. A list of symbols, or of
|
|
770
|
+
strings under `# frozen_string_literal: true`, cannot be changed, so its block
|
|
771
|
+
is unrolled unless it assigns the variable.
|
|
772
|
+
- **A static route to an app a constant holds is drawn where Rails draws it.**
|
|
773
|
+
`mount APPS[:admin] => "/admin"` and `mount HANDLERS[:x], at: "/x"` are listed
|
|
774
|
+
at their path with an inferred app, where they were named `APPS.[]` and
|
|
775
|
+
`HANDLERS.[]`. A verb route to such a value, such as
|
|
776
|
+
`get "y", to: HANDLERS[:x]` or `get "y" => HANDLERS[:x]`, counts as a
|
|
777
|
+
construct the static tier could not expand: it was listed as a mounted app
|
|
778
|
+
named `HANDLERS.[]`, so the table showed one route fewer than Rails draws. A
|
|
779
|
+
verb route to a constant the routes file assigns once, at its top level,
|
|
780
|
+
inside a module, through a path such as `Cfg::HOME =` or with `||=`, reads it
|
|
781
|
+
as Ruby looks the reference up, through Module.nesting where it is read and
|
|
782
|
+
then the top level: with `TO = "posts#index"`, `get "a", to: TO` is a route to
|
|
783
|
+
posts#index, `ALIAS = HealthCheck` names the app HealthCheck, and a lambda
|
|
784
|
+
counts as a construct the static tier could not expand. An app the file builds
|
|
785
|
+
with `.new` is named as booted Rails names it: an instance
|
|
786
|
+
(`Rack::Builder.new { ... }`, `HealthCheck.new`, `AL.new` with
|
|
787
|
+
`AL = HealthCheck`) by its class, a `Class.new` by its constant. A constant
|
|
788
|
+
holding any other call reads as that call written in place: a verb route to
|
|
789
|
+
`"posts#index".freeze` or `ENV.fetch(...)` counts as a construct the static
|
|
790
|
+
tier could not expand, a mount of `HealthCheck.app(1)` held in a constant is
|
|
791
|
+
named `HealthCheck.app`, one of `APPS[:a]` is listed with an `[INFERRED]` app,
|
|
792
|
+
and one of a call with a block (`Rack::Builder.app do`) counts as not
|
|
793
|
+
expanded. A verb route to a bare all-caps constant the file does not assign,
|
|
794
|
+
or assigns more than once, is no longer listed as a mounted app.
|
|
795
|
+
- **A static route option given as a constant is no longer read as the
|
|
796
|
+
constant's name.** A `controller:`, `action:`, `as:`, `param:` or
|
|
797
|
+
`shallow_prefix:` given as a constant is marked inferred, in the names of the
|
|
798
|
+
shallow routes under it too. A scope, namespace, resource or route whose path,
|
|
799
|
+
module, verbs, `shallow:` or `on:` come from a constant, such as
|
|
800
|
+
`scope path: PREFIX`, `namespace :api, module: MOD`, `match "x", via: VERBS`
|
|
801
|
+
or `get :preview, on: ON`, or from a computed prefix (`scope PREFIX do`),
|
|
802
|
+
counts as a construct the static tier could not expand.
|
|
803
|
+
- **A static verb route to a redirect, a held value or a Rack app takes the name
|
|
804
|
+
Rails gives it.** So does a mount, so a later `get "health_check"` after
|
|
805
|
+
`mount HealthCheck` is unnamed: an engine takes the railtie name its file
|
|
806
|
+
gives it (an isolated engine its namespace's), a constant ending in `Engine`
|
|
807
|
+
whose file is not in the app takes none, a bare `mount UI` inside the
|
|
808
|
+
`module Admin` that declares it is named Admin::UI and takes admin_ui, and
|
|
809
|
+
`as: nil` leaves a mount unnamed on Rails 8.1, and when no Rails version is
|
|
810
|
+
known, and named before 8.1. The route drawn at the same path or name after it
|
|
811
|
+
is unnamed, as it is live, where it used to take the name.
|
|
812
|
+
- **A resource's own `shallow_path:` and `shallow_prefix:` place and name its
|
|
813
|
+
shallow routes and its children's, as the scope Rails draws it in does.**
|
|
814
|
+
`resources :posts, shallow_path: "sp"` with a shallow child lists `/sp/c/:id`,
|
|
815
|
+
where it listed `/c/:id`.
|
|
816
|
+
- **A mount inside `scope nil` keeps its path.** `scope nil` adds no path
|
|
817
|
+
segment, as Rails reads it.
|
|
818
|
+
- **A Rack app mounted or matched inside `resources`, `member` or `collection`
|
|
819
|
+
is listed at the path Rails attaches it, such as `/posts/:post_id/hc`.** It
|
|
820
|
+
was listed at the bare `at:` path. One inside a resource the static walk
|
|
821
|
+
cannot read, or a block in a resource that does not draw in place, has no
|
|
822
|
+
known path.
|
|
823
|
+
- **A Rack app in a routing concern, a routes-file method, a literal list's
|
|
824
|
+
loop or a `resources :a, :b` block is listed at each place Rails draws it,
|
|
825
|
+
with that place's path.** A concern's app is listed at each `concerns` call
|
|
826
|
+
and `concerns:` option (`/adm/hc` under `namespace :adm`, `/posts/:post_id/hc`
|
|
827
|
+
on a resource), with the conditions around that call, then those the concern
|
|
828
|
+
or method is defined under, then its own. A concern or method defined in each
|
|
829
|
+
arm of an `if`/`else` or `case` is drawn at the site once per arm, under that
|
|
830
|
+
arm's condition; a later definition replaces an earlier one when every branch
|
|
831
|
+
it is written under is one the earlier one is written under (a branch counts
|
|
832
|
+
by its text, so a second `if ENV["A"]` block is the same branch), and an
|
|
833
|
+
earlier one that a later definition under one `if`/`unless` may replace
|
|
834
|
+
carries the opposite of that branch. Each arm names its routes from the names
|
|
835
|
+
taken before the site, and a later route finds a name taken if either arm
|
|
836
|
+
took it. A loop's `at:`, path, scope or
|
|
837
|
+
`as:` is read through the variable it binds. A concern the file never applies,
|
|
838
|
+
applies only in a file another `draw` reads, or redefines before any
|
|
839
|
+
`concerns` call still lists its apps once with no path, and a method called
|
|
840
|
+
with arguments lists them once where it is written. A constant a replayed body
|
|
841
|
+
writes counts as one write.
|
|
842
|
+
- **A route in a routing concern or routes-file method defined under
|
|
843
|
+
`if`/`unless`/`case` carries that condition, and a concern defined in each arm
|
|
844
|
+
draws each arm's routes.** Only the last arm's routes were listed, with no
|
|
845
|
+
condition.
|
|
846
|
+
- **A mount path written as a symbol is the path Rails draws.**
|
|
847
|
+
`mount App, at: :hc`, `mount App => :hc` and `get :metrics, to: App` list
|
|
848
|
+
`/hc` and `/metrics`, where they listed no path. A symbol naming a canonical
|
|
849
|
+
action inside a member, collection or singular resource scope, or with `on:`,
|
|
850
|
+
sits at the scope's own path (`get :index, to: App` in a collection is
|
|
851
|
+
`/posts`), as a verb route's symbol does; a string keeps its segment.
|
|
852
|
+
- **The booted tier names a module mounted as a Rack app by the module's name,
|
|
853
|
+
as `rails routes` prints it.** It printed `Module`.
|
|
854
|
+
- **A replayed column of a native type name reads the way the adapter reads it
|
|
855
|
+
back.** On PostgreSQL, `t.column :ratio, :real` is a float of limit 24 on
|
|
856
|
+
Rails 7.2.2 and later, and an array spelling such as `text[]` stays an array
|
|
857
|
+
of its element type. A MySQL `double`, `real` or `float8` is a float of limit
|
|
858
|
+
53, and MySQL's `serial` is a not null unsigned bigint, as a key or a column,
|
|
859
|
+
and the unique index MySQL adds for a serial column is listed under its name,
|
|
860
|
+
or `<name>_2` when that is taken. A `tinyint(1)` is a boolean on MySQL and an
|
|
861
|
+
integer of limit 1 on SQLite. An `int2` is an integer of limit 2, a
|
|
862
|
+
`varchar(40)` a string of limit 40, and a `bool` a boolean, as Rails' dump
|
|
863
|
+
writes them. A MySQL table an `execute` creates reads with MySQL's rules, and
|
|
864
|
+
an adapter with no family reads with PostgreSQL's.
|
|
865
|
+
- **A PostgreSQL serial reads by the same name from schema.rb, structure.sql, a
|
|
866
|
+
replayed migration and the booted tier.** A column whose nextval default names
|
|
867
|
+
its own `<table>_<column>_seq` is a `serial`, or a `bigserial` when it is a
|
|
868
|
+
bigint, as Rails' dump writes it. An integer key `create_table` makes is a
|
|
869
|
+
serial unless a default is given or Migration[5.0] gives it one. A bigserial
|
|
870
|
+
that is the table's whole key reads as the default bigint key, while a
|
|
871
|
+
bigserial column of a composite key stays a bigserial. `change_column` keeps a
|
|
872
|
+
serial a serial whatever the new type, until a new default replaces the
|
|
873
|
+
sequence, and keeps a key with no sequence of its own
|
|
874
|
+
(`id: :bigint, default: nil`, a `Migration[5.0]` key, an `execute`'s
|
|
875
|
+
`bigint PRIMARY KEY`) an integer. A key `create_table` declares `serial` or
|
|
876
|
+
`smallserial` stays a serial through `change_column`. Once a serial's sequence
|
|
877
|
+
no longer follows its name (after `rename_column`, after `rename_table` for
|
|
878
|
+
any column but the key, after `change_column_default`, or in a
|
|
879
|
+
`CREATE TABLE AS` copy) it reads as the integer the dump writes, with the old
|
|
880
|
+
sequence as its default where one stays. A serial renamed away and back is a
|
|
881
|
+
serial again, as the dump reads a serial by the names it has at the end, and a
|
|
882
|
+
serial that `change_column` retypes once its sequence no longer follows its
|
|
883
|
+
names keeps the new type. A new serial whose `<table>_<column>_seq` a table,
|
|
884
|
+
an index or a renamed-away serial already holds gets the `_seq1` (or the next
|
|
885
|
+
free) sequence PostgreSQL picks, and every path reads a numbered `seq` suffix
|
|
886
|
+
as the serial's own. `rename_table` renames the key's sequence only where the
|
|
887
|
+
app's Rails version would: 7.1 to 8.1 cut the table name to fit PostgreSQL's
|
|
888
|
+
63 bytes, while 7.0 does not and PostgreSQL cuts the result. A nextval of any
|
|
889
|
+
other sequence stays the column's default, and an INHERITS child, from
|
|
890
|
+
structure.sql or a replayed `execute`, gets its parent's columns that way, the
|
|
891
|
+
default bigint key's nextval included.
|
|
892
|
+
- **A replayed PostgreSQL child table follows the parent it INHERITS.** A
|
|
893
|
+
parent's `change_column`, `change_column_default` or `change_column_null` runs
|
|
894
|
+
on each child's column as PostgreSQL applies it, even where the parent already
|
|
895
|
+
had that value; a column the parent adds or renames changes in the child too,
|
|
896
|
+
one the parent drops goes unless the child declares it, and a renamed sequence
|
|
897
|
+
shows in the child's default. A child that declares one of its parent's
|
|
898
|
+
columns NOT NULL keeps it NOT NULL until such a statement on the parent
|
|
899
|
+
reaches it. A `create_table` with `INHERITS (...)` in its options gets its
|
|
900
|
+
parents' columns and keeps a parent it cannot find as unresolved, as an
|
|
901
|
+
executed CREATE TABLE does, and a drop under CASCADE drops the tables that
|
|
902
|
+
inherit from it and the foreign keys to it.
|
|
903
|
+
- **A replayed MySQL column is unsigned where the statement that makes it says
|
|
904
|
+
so.** A key, a column, a reference or an `unsigned_integer` keeps it, and
|
|
905
|
+
`change_column` drops it unless the call gives it again, as Rails rebuilds the
|
|
906
|
+
column from the call's options.
|
|
907
|
+
- **A replayed MySQL or MariaDB auto-increment column, a serial among them,
|
|
908
|
+
stays NOT NULL through `change_column_null` and `change_column`, as the server
|
|
909
|
+
keeps it.** `auto_increment: false` releases it.
|
|
910
|
+
- **A replayed reference's id column reads its type and sizes as the dump writes
|
|
911
|
+
them.** `type: :unsigned_integer` is an unsigned integer on MySQL, and
|
|
912
|
+
`limit:` reads as the dump writes it, in `create_join_table` too.
|
|
913
|
+
- **A key column a migration declares reads as the key `create_table` implies.**
|
|
914
|
+
`t.primary_key` with or without a type, `primary_key: true` and `add_column`
|
|
915
|
+
of a `primary_key` type give a not null key of the declared type, and an
|
|
916
|
+
integer key its own sequence on PostgreSQL.
|
|
917
|
+
- **A replayed composite primary key's columns are not null on PostgreSQL and
|
|
918
|
+
MySQL, as those databases make them.** SQLite leaves them nullable.
|
|
919
|
+
- **A replayed `timestamp` reads as the datetime the PostgreSQL and SQLite dumps
|
|
920
|
+
write, and stays a timestamp on MySQL.** A time, timestamp or timestamptz
|
|
921
|
+
keeps the precision schema.rb writes on every path: only a datetime's 6 is
|
|
922
|
+
left out, and only MySQL drops a 0. A dump that writes `precision: nil`
|
|
923
|
+
(PostgreSQL's plain timestamp, MySQL's `datetime(0)`) reads with no precision.
|
|
924
|
+
- **A replayed `create_table` the migration skips stays skipped.** A temporary
|
|
925
|
+
table named by a string no longer appears in the replayed schema, and
|
|
926
|
+
`if_not_exists: true` on a table that exists no longer adds the block's
|
|
927
|
+
columns to it. An executed `CREATE TABLE IF NOT EXISTS` leaves an existing
|
|
928
|
+
table as it is, and a `create_table ..., force:` replaces the table it names.
|
|
929
|
+
- **A replayed PostgreSQL nextval default reads as the dump writes it, and
|
|
930
|
+
follows a key sequence that `rename_table` renames.** One that names the
|
|
931
|
+
column's own sequence is a serial.
|
|
932
|
+
- **A replayed column whose default is nil has no default, as the dump writes
|
|
933
|
+
it, and a `change_column_default` or `add_column` with a proc default keeps
|
|
934
|
+
that default.** A reverted `change_column_default` sets its `from:` value. A
|
|
935
|
+
proc default reads as the dump writes it (`-> { "now()" }`), and one whole
|
|
936
|
+
PostgreSQL literal in one, such as `-> { "'y'::character varying" }`, reads as
|
|
937
|
+
Rails decodes it, booleans, arrays and dates through the column's type, while
|
|
938
|
+
any other expression keeps its proc; a bare MySQL literal or number in one
|
|
939
|
+
reads as its value. A replayed SQLite proc default reads as the app's Rails
|
|
940
|
+
reads it back: a quoted or bare literal is its value, and an expression with
|
|
941
|
+
no function is no default. A boolean proc default goes through Rails' boolean
|
|
942
|
+
cast on SQLite and MySQL, PostgreSQL's `TRUE`, `('a')::text` and boolean array
|
|
943
|
+
words read as the server stores them, and a bare MySQL number reads as MySQL
|
|
944
|
+
stores it for the column's type (`2.5` on an integer reads `3`, `007` on a
|
|
945
|
+
string `7`, `1e3` anywhere `1000`, a decimal's rounded to the column's scale
|
|
946
|
+
with no negative zero, `07` on a year `2007` whether create_table or
|
|
947
|
+
change_column_default sets it, and one with a trailing point such as `1.` as
|
|
948
|
+
its value); on a date or time column, for a value MySQL refuses, and for one
|
|
949
|
+
no double holds on a float or double column, it keeps its proc, and the rest
|
|
950
|
+
of the migration file still replays.
|
|
951
|
+
- **A UNIQUE that a replayed or hand-written CREATE TABLE declares is the key
|
|
952
|
+
the database makes.** MySQL lists an index named for its first column, and
|
|
953
|
+
PostgreSQL one constraint per column list named `<table>_<columns>_key`, or
|
|
954
|
+
the unique index behind it for a Rails 7.0 app.
|
|
955
|
+
- **The migration replay reads Rails' PostgreSQL unique constraints.**
|
|
956
|
+
`t.unique_constraint` in `create_table` and `change_table`,
|
|
957
|
+
`add_unique_constraint` and `remove_unique_constraint` add and drop the
|
|
958
|
+
constraint the dump lists, and `using_index:` takes the index over.
|
|
959
|
+
- **A unique constraint's `nulls_not_distinct` shows on every schema path.**
|
|
960
|
+
structure.sql, a replayed migration, schema.rb and the booted connection read
|
|
961
|
+
it as Rails 8.0 and later dump it, Rails 7.1 and 7.2 dumps leave it out, and
|
|
962
|
+
`rails_get_schema` prints it beside the constraint. A structure.sql index
|
|
963
|
+
reads `NULLS NOT DISTINCT` only for Rails 7.1 on, since Rails 7.0 cannot dump
|
|
964
|
+
the option.
|
|
965
|
+
- **A replayed decimal reads as the dump writes it.** A precision with no scale
|
|
966
|
+
is scale 0 on every adapter, and a default at scale 0 is whole as Rails casts
|
|
967
|
+
it (`2.5` reads `2`), while MySQL's own `decimal(10,0)` still rounds a default
|
|
968
|
+
the server gets unscaled; any other default reads in the column's scale
|
|
969
|
+
(`"0.0"`). A `numeric` column is a decimal with no sizes, as `type_to_sql`
|
|
970
|
+
writes it, and MySQL's plain `decimal` has precision 10.
|
|
971
|
+
- **Array and decimal defaults read as Rails dumps them on every schema path.**
|
|
972
|
+
Each PostgreSQL array element goes through the column's type, with NULL as
|
|
973
|
+
nil, quotes and backslashes decoded and floats as floats, and an `ARRAY[...]`
|
|
974
|
+
default is no longer cut at its first comma. A decimal default is rounded to
|
|
975
|
+
its scale and cut to a whole number at scale 0, so `numeric(5) DEFAULT 2.5`
|
|
976
|
+
reads `2`. A replayed PostgreSQL array default written as a Ruby array or an
|
|
977
|
+
array-literal string reads the same way, so `[1, 2.5]` on a float column reads
|
|
978
|
+
`[1.0, 2.5]` and `"{a,b}"` reads `["a", "b"]`.
|
|
979
|
+
- **A PostgreSQL column default that Rails cannot read as a value or a function
|
|
980
|
+
shows as no default, as schema.rb has it.** The structure.sql reader and the
|
|
981
|
+
migration replay showed `ARRAY[...]`, `'a' || 'b'`, `(1 + 1)`, `CURRENT_TIME`
|
|
982
|
+
and similar expressions as `-> { ... }`; they now follow Rails'
|
|
983
|
+
`has_default_function?` and give a proc only for what it accepts.
|
|
984
|
+
- **A structure.sql column with a quoted name that is not a plain word is
|
|
985
|
+
read**, PostgreSQL's `?column?` and names with spaces or doubled quotes among
|
|
986
|
+
them, with their `ALTER COLUMN` defaults.
|
|
987
|
+
- **A MySQL `CREATE TABLE` in a migration's `execute` or a hand-written
|
|
988
|
+
structure.sql keeps an index written without quotes or a name as an index.**
|
|
989
|
+
`INDEX (a)`, `KEY k (b)` and `FULLTEXT (t)` read as made-up columns. They now
|
|
990
|
+
take the name MySQL gives them, and in a structure.sql a functional key reads
|
|
991
|
+
as Rails' connection reads it.
|
|
992
|
+
- **A table a migration's `execute` makes with `CREATE TABLE ... AS SELECT`, or
|
|
993
|
+
`create_table` with `as:`, is read with the columns its database gives it,
|
|
994
|
+
where the replay can trace them.** MySQL keeps their not null and defaults,
|
|
995
|
+
PostgreSQL keeps neither, and SQLite types each column by its affinity. A
|
|
996
|
+
column it cannot trace stays untyped, and a column it cannot name or a query
|
|
997
|
+
it cannot read is counted in the schema note, so such a table never reads as
|
|
998
|
+
complete when it is not. It reads the query's own FROM, outside parentheses
|
|
999
|
+
and quotes, reads past DISTINCT and keeps the column `DISTINCT ON (k)` or
|
|
1000
|
+
`DISTINCT(k)` names, and reads each select item with the database's own
|
|
1001
|
+
quotes and numbers (dollar-quoted strings, `0x` and `0b` numbers, `x''`
|
|
1002
|
+
blobs, a timestamp's precision, interval fields), so it keeps a bare alias
|
|
1003
|
+
after any complete expression: a quoted one with every character in it, a
|
|
1004
|
+
string one on MySQL and SQLite, a bare PostgreSQL one folded to lower case,
|
|
1005
|
+
and a keyword that ends the item (`time w` names `w`). MySQL reads adjacent
|
|
1006
|
+
strings as one, named by the first (`'a' 'b'` names `a`), and a number with a
|
|
1007
|
+
leading zero reads in base ten. A computed column is named as each database
|
|
1008
|
+
names it: on PostgreSQL as its FigureColname does (a function's name, a
|
|
1009
|
+
cast's argument or its type, a CASE's ELSE or `case`, a scalar subquery its
|
|
1010
|
+
own first column's name, a subquery selecting a star unnamed,
|
|
1011
|
+
`IS NORMALIZED` `is_normalized`, `?column?` for an operator or a constant),
|
|
1012
|
+
on MySQL and SQLite by its text. A literal column
|
|
1013
|
+
is typed as MySQL 8.4's dump types it; MariaDB gives one no default and types
|
|
1014
|
+
some integers smaller, which the replay cannot tell apart. MySQL's form
|
|
1015
|
+
without AS makes its table, a MySQL column list before the SELECT keeps the
|
|
1016
|
+
keys it declares and orders the columns as MySQL does (the declared ones the
|
|
1017
|
+
query does not select, then the selected ones), and a query in parentheses
|
|
1018
|
+
reads its own columns.
|
|
1019
|
+
- **A migration's `create_table` or `change_table` block reads its columns,
|
|
1020
|
+
indexes and references whatever its parameter is named, `it` and `_1`
|
|
1021
|
+
included.** Rails hands the block the table under any name, but the static
|
|
1022
|
+
tier read a block's calls only on `t`, so `do |table|` added an empty table. A
|
|
1023
|
+
block parameter of any other kind (`reversible do |dir|`,
|
|
1024
|
+
`find_each { |t| ... }`) is still not read as the table.
|
|
1025
|
+
- **A SQLite structure.sql types each column the way Rails' SQLite type map
|
|
1026
|
+
reads it.** A `REAL`, `NUM` or other type the map does not match reads with no
|
|
1027
|
+
type, as booted Rails reads it, where it read as float or as its own name;
|
|
1028
|
+
`number`, `clob`, `point`, `nvarchar`, `jsonb` and `smalldatetime` read as
|
|
1029
|
+
decimal, text, integer, string, json and datetime; an uppercase `BIGINT` reads
|
|
1030
|
+
as integer, as the dumper writes it; and a column that declares no type is
|
|
1031
|
+
listed, not dropped. An `UNSIGNED BIG INT` reads as a plain integer, since
|
|
1032
|
+
only MySQL's columns carry an unsigned mark, and a default reads as Rails'
|
|
1033
|
+
SQLite adapter reads it: without the parentheses SQLite drops, a boolean's
|
|
1034
|
+
`1`, `0`, `'t'` or `'f'` as Rails' boolean type casts it, an empty string as
|
|
1035
|
+
no default, and an expression that is no function as no default. A primary key
|
|
1036
|
+
declared without NOT NULL allows NULL, as SQLite and booted Rails read it. A
|
|
1037
|
+
`DEFAULT TRUE` or `DEFAULT FALSE` reads as the app's Rails dumps it: no
|
|
1038
|
+
default before 8.1; on 8.1.0 to 8.1.3 the uppercase words, which a number
|
|
1039
|
+
column casts to 0 or 0.0 and a date or time column to none; and on 8.1.4
|
|
1040
|
+
either case as a boolean, which a string column dumps as "t" or "f", a float
|
|
1041
|
+
or decimal as 1.0 or 0.0, and an integer, a whole decimal or a date or time
|
|
1042
|
+
column as no default.
|
|
1043
|
+
- **A MySQL structure.sql column of a type the reader does not name is typed as
|
|
1044
|
+
Rails reads it.** A `point` or `multipoint` column and a `year` read as
|
|
1045
|
+
integers, as Rails' MySQL type map matches them, a `bit` reads as binary with
|
|
1046
|
+
its limit, and a `geometry` has no type, as booted Rails gives. A bare `bit`
|
|
1047
|
+
reads as the `bit(1)` MySQL makes it, limit 1, in a structure.sql, an executed
|
|
1048
|
+
CREATE TABLE and a replayed migration, and a MariaDB `uuid` has no type, as
|
|
1049
|
+
booted Rails reads it.
|
|
1050
|
+
- **A migration helper module assigned with `Module.new`, such as
|
|
1051
|
+
`MigrationHelpers = Module.new do`, replays the helpers it defines as a
|
|
1052
|
+
`module` statement does.** A migration that includes it no longer drops the
|
|
1053
|
+
columns those helpers add from the replayed schema.
|
|
1054
|
+
- **A migration's `self.execute` replays like a bare `execute`, and a migration
|
|
1055
|
+
table helper class whose name keeps an underscore, such as `Shop_Items`, names
|
|
1056
|
+
its table by its file.**
|
|
1057
|
+
|
|
1058
|
+
### Removed
|
|
1059
|
+
|
|
1060
|
+
- **An expression index read from `db/schema.rb` no longer carries an
|
|
1061
|
+
`expression: true` key.** It now has the same name, columns and uniqueness
|
|
1062
|
+
shape as the structure.sql reader and the booted tier give it. (#605)
|
|
1063
|
+
- **`ActionResolver.own_actions`, `PortablePath.relativize_all` and
|
|
1064
|
+
`McpConfigGenerator.sub_table` are gone.** Nothing called them. (#518)
|
|
1065
|
+
- **`McpController.mcp_transport` and `.reset_transport!` are gone.** They only
|
|
1066
|
+
forwarded to `McpEdge`. The bind warning and the Host check now read one
|
|
1067
|
+
loopback list. (#603)
|
|
1068
|
+
|
|
8
1069
|
## [5.36.9] - 2026-10-23
|
|
9
1070
|
|
|
10
1071
|
### Security
|