rails-ai-context 5.30.2 → 5.32.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 +868 -0
- data/CONTEXT.md +7 -3
- data/README.md +1 -1
- data/docs/ARCHITECTURE.md +3 -3
- data/docs/CLI.md +1 -1
- data/docs/COMPATIBILITY.md +50 -14
- data/docs/CONFIGURATION.md +4 -1
- data/docs/FAQ.md +1 -1
- data/docs/GUIDE.md +14 -8
- data/docs/INTROSPECTORS.md +50 -23
- data/docs/SECURITY.md +56 -1
- data/docs/TOOLS.md +58 -12
- data/lib/generators/rails_ai_context/install/install_generator.rb +1 -0
- data/lib/rails_ai_context/action_filters.rb +121 -48
- data/lib/rails_ai_context/app_commands.rb +2 -2
- data/lib/rails_ai_context/app_kind.rb +65 -8
- data/lib/rails_ai_context/ast_cache.rb +53 -18
- data/lib/rails_ai_context/base_mixins.rb +46 -16
- data/lib/rails_ai_context/boot_manager.rb +5 -1
- data/lib/rails_ai_context/cli/entry_boot.rb +17 -2
- data/lib/rails_ai_context/cli/tool_runner.rb +15 -1
- data/lib/rails_ai_context/concern_macros.rb +791 -111
- data/lib/rails_ai_context/concern_membership.rb +25 -5
- data/lib/rails_ai_context/concern_paths.rb +63 -17
- data/lib/rails_ai_context/config_yaml.rb +40 -0
- data/lib/rails_ai_context/configuration.rb +3 -3
- data/lib/rails_ai_context/data/docs/index.json +324 -0
- data/lib/rails_ai_context/database_yml.rb +136 -0
- data/lib/rails_ai_context/doctor.rb +30 -37
- data/lib/rails_ai_context/erb_source.rb +40 -0
- data/lib/rails_ai_context/fingerprinter.rb +48 -22
- data/lib/rails_ai_context/fixture_keys.rb +42 -1
- data/lib/rails_ai_context/gem_lock.rb +275 -47
- data/lib/rails_ai_context/git_ignore.rb +13 -11
- data/lib/rails_ai_context/hydrators/hydration_formatter.rb +8 -2
- data/lib/rails_ai_context/hydrators/schema_hint_builder.rb +10 -5
- data/lib/rails_ai_context/introspector.rb +10 -1
- data/lib/rails_ai_context/introspectors/action_mailbox_introspector.rb +27 -19
- data/lib/rails_ai_context/introspectors/action_presence.rb +11 -8
- data/lib/rails_ai_context/introspectors/action_resolver.rb +38 -26
- data/lib/rails_ai_context/introspectors/action_text_introspector.rb +2 -2
- data/lib/rails_ai_context/introspectors/active_record_settings.rb +56 -0
- data/lib/rails_ai_context/introspectors/active_storage_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/active_support_introspector.rb +98 -22
- data/lib/rails_ai_context/introspectors/admin_resources.rb +101 -0
- data/lib/rails_ai_context/introspectors/anyway_configs.rb +64 -0
- data/lib/rails_ai_context/introspectors/apartment_config.rb +47 -0
- data/lib/rails_ai_context/introspectors/api_introspector.rb +125 -26
- data/lib/rails_ai_context/introspectors/asset_pipeline_introspector.rb +24 -19
- data/lib/rails_ai_context/introspectors/ast_walk.rb +18 -0
- data/lib/rails_ai_context/introspectors/auth_introspector.rb +71 -18
- data/lib/rails_ai_context/introspectors/autoload_introspector.rb +62 -15
- data/lib/rails_ai_context/introspectors/base.rb +38 -0
- data/lib/rails_ai_context/introspectors/call_site_expansion.rb +349 -39
- data/lib/rails_ai_context/introspectors/component_introspector.rb +26 -14
- data/lib/rails_ai_context/introspectors/config_introspector.rb +22 -9
- data/lib/rails_ai_context/introspectors/connection_pool_introspector.rb +1 -4
- data/lib/rails_ai_context/introspectors/controller_filters.rb +335 -31
- data/lib/rails_ai_context/introspectors/controller_introspector.rb +315 -129
- data/lib/rails_ai_context/introspectors/controller_settings.rb +182 -0
- data/lib/rails_ai_context/introspectors/convention_introspector.rb +18 -15
- data/lib/rails_ai_context/introspectors/database_stats_introspector.rb +1 -1
- data/lib/rails_ai_context/introspectors/declared_constant.rb +108 -23
- data/lib/rails_ai_context/introspectors/eager_load.rb +12 -7
- data/lib/rails_ai_context/introspectors/engine_introspector.rb +13 -5
- data/lib/rails_ai_context/introspectors/env_config_introspector.rb +62 -17
- data/lib/rails_ai_context/introspectors/env_introspector.rb +8 -6
- data/lib/rails_ai_context/introspectors/env_references.rb +21 -4
- data/lib/rails_ai_context/introspectors/frontend_framework_introspector.rb +37 -22
- data/lib/rails_ai_context/introspectors/gem_introspector.rb +3 -1
- data/lib/rails_ai_context/introspectors/gemfile_gems.rb +53 -5
- data/lib/rails_ai_context/introspectors/grape_endpoints.rb +109 -0
- data/lib/rails_ai_context/introspectors/helper_names.rb +12 -1
- data/lib/rails_ai_context/introspectors/i18n_introspector.rb +101 -46
- data/lib/rails_ai_context/introspectors/includers.rb +6 -1
- data/lib/rails_ai_context/introspectors/initializer_introspector.rb +2 -5
- data/lib/rails_ai_context/introspectors/job_introspector.rb +548 -103
- data/lib/rails_ai_context/introspectors/listener_registration.rb +11 -1
- data/lib/rails_ai_context/introspectors/listeners/associations_listener.rb +51 -4
- data/lib/rails_ai_context/introspectors/listeners/autoload_ignore_listener.rb +31 -0
- data/lib/rails_ai_context/introspectors/listeners/autoload_paths_listener.rb +4 -47
- data/lib/rails_ai_context/introspectors/listeners/base_listener.rb +80 -14
- data/lib/rails_ai_context/introspectors/listeners/branch_conditions.rb +156 -0
- data/lib/rails_ai_context/introspectors/listeners/callbacks_listener.rb +85 -23
- data/lib/rails_ai_context/introspectors/listeners/class_definition_listener.rb +35 -6
- data/lib/rails_ai_context/introspectors/listeners/component_structure_listener.rb +30 -2
- data/lib/rails_ai_context/introspectors/listeners/conditional_macro_listener.rb +19 -0
- data/lib/rails_ai_context/introspectors/listeners/config_assignment_listener.rb +255 -10
- data/lib/rails_ai_context/introspectors/listeners/constant_reference_listener.rb +34 -0
- data/lib/rails_ai_context/introspectors/listeners/constructor_macro_listener.rb +123 -0
- data/lib/rails_ai_context/introspectors/listeners/definition_file_paths_listener.rb +66 -0
- data/lib/rails_ai_context/introspectors/listeners/enums_listener.rb +2 -2
- data/lib/rails_ai_context/introspectors/listeners/env_access_listener.rb +31 -0
- data/lib/rails_ai_context/introspectors/listeners/filter_macro_listener.rb +54 -0
- data/lib/rails_ai_context/introspectors/listeners/fixture_paths_listener.rb +62 -0
- data/lib/rails_ai_context/introspectors/listeners/gemfile_dsl_listener.rb +42 -1
- data/lib/rails_ai_context/introspectors/listeners/generic_macro_listener.rb +28 -2
- data/lib/rails_ai_context/introspectors/listeners/grape_api_listener.rb +137 -0
- data/lib/rails_ai_context/introspectors/listeners/http_client_call_listener.rb +50 -0
- data/lib/rails_ai_context/introspectors/listeners/i18n_load_path_listener.rb +54 -0
- data/lib/rails_ai_context/introspectors/listeners/literal_paths.rb +123 -0
- data/lib/rails_ai_context/introspectors/listeners/macros_listener.rb +248 -4
- data/lib/rails_ai_context/introspectors/listeners/mailbox_routing_listener.rb +2 -1
- data/lib/rails_ai_context/introspectors/listeners/method_call_listener.rb +7 -1
- data/lib/rails_ai_context/introspectors/listeners/methods_listener.rb +352 -69
- data/lib/rails_ai_context/introspectors/listeners/migration_dsl_listener.rb +1 -1
- data/lib/rails_ai_context/introspectors/listeners/migration_replay_listener.rb +35 -26
- data/lib/rails_ai_context/introspectors/listeners/mixins_listener.rb +62 -29
- data/lib/rails_ai_context/introspectors/listeners/mount_listener.rb +8 -0
- data/lib/rails_ai_context/introspectors/listeners/namespaced_roots_listener.rb +35 -0
- data/lib/rails_ai_context/introspectors/listeners/nested_constants_listener.rb +32 -0
- data/lib/rails_ai_context/introspectors/listeners/owner_scope.rb +67 -0
- data/lib/rails_ai_context/introspectors/listeners/preview_paths_listener.rb +16 -7
- data/lib/rails_ai_context/introspectors/listeners/proc_literal_listener.rb +42 -0
- data/lib/rails_ai_context/introspectors/listeners/queue_assignment_listener.rb +54 -0
- data/lib/rails_ai_context/introspectors/listeners/rake_task_dsl_listener.rb +21 -8
- data/lib/rails_ai_context/introspectors/listeners/route_files_listener.rb +4 -11
- data/lib/rails_ai_context/introspectors/listeners/routes_dsl_listener.rb +461 -65
- data/lib/rails_ai_context/introspectors/listeners/schema_dsl_listener.rb +76 -14
- data/lib/rails_ai_context/introspectors/listeners/scopes_listener.rb +40 -1
- data/lib/rails_ai_context/introspectors/listeners/validations_listener.rb +10 -1
- data/lib/rails_ai_context/introspectors/listeners/view_paths_listener.rb +37 -0
- data/lib/rails_ai_context/introspectors/middleware_introspector.rb +47 -3
- data/lib/rails_ai_context/introspectors/migration_introspector.rb +3 -3
- data/lib/rails_ai_context/introspectors/migration_replay/helpers.rb +4 -3
- data/lib/rails_ai_context/introspectors/migration_replay/statements.rb +72 -12
- data/lib/rails_ai_context/introspectors/migration_replay.rb +110 -14
- data/lib/rails_ai_context/introspectors/model_introspector.rb +708 -214
- data/lib/rails_ai_context/introspectors/module_aliases.rb +11 -3
- data/lib/rails_ai_context/introspectors/multi_database_introspector.rb +9 -57
- data/lib/rails_ai_context/introspectors/observability_introspector.rb +5 -8
- data/lib/rails_ai_context/introspectors/performance_introspector.rb +28 -8
- data/lib/rails_ai_context/introspectors/rake_task_introspector.rb +118 -51
- data/lib/rails_ai_context/introspectors/recurring_schedules.rb +150 -0
- data/lib/rails_ai_context/introspectors/rendered_record.rb +87 -0
- data/lib/rails_ai_context/introspectors/retry_policy.rb +8 -5
- data/lib/rails_ai_context/introspectors/route_introspector.rb +153 -42
- data/lib/rails_ai_context/introspectors/schema_conventions.rb +127 -40
- data/lib/rails_ai_context/introspectors/schema_dump_path.rb +80 -0
- data/lib/rails_ai_context/introspectors/schema_introspector.rb +346 -112
- data/lib/rails_ai_context/introspectors/schema_reader.rb +69 -29
- data/lib/rails_ai_context/introspectors/service_classes.rb +21 -15
- data/lib/rails_ai_context/introspectors/source_calls.rb +1 -1
- data/lib/rails_ai_context/introspectors/source_introspector.rb +17 -2
- data/lib/rails_ai_context/introspectors/source_scan.rb +174 -26
- data/lib/rails_ai_context/introspectors/sqlite_virtual_tables.rb +28 -0
- data/lib/rails_ai_context/introspectors/stimulus_introspector.rb +16 -5
- data/lib/rails_ai_context/introspectors/structure_sql_reader.rb +372 -98
- data/lib/rails_ai_context/introspectors/superclass_chain.rb +14 -15
- data/lib/rails_ai_context/introspectors/table_name.rb +46 -45
- data/lib/rails_ai_context/introspectors/test_introspector.rb +289 -78
- data/lib/rails_ai_context/introspectors/turbo_introspector.rb +10 -5
- data/lib/rails_ai_context/introspectors/view_introspector.rb +5 -40
- data/lib/rails_ai_context/introspectors/view_template_introspector.rb +12 -10
- data/lib/rails_ai_context/method_name.rb +31 -0
- data/lib/rails_ai_context/migration_status.rb +5 -5
- data/lib/rails_ai_context/output_guard.rb +6 -4
- data/lib/rails_ai_context/package_json.rb +96 -5
- data/lib/rails_ai_context/path_resolver.rb +170 -15
- data/lib/rails_ai_context/payload.rb +48 -0
- data/lib/rails_ai_context/pending_migrations.rb +15 -18
- data/lib/rails_ai_context/portable_path.rb +76 -4
- data/lib/rails_ai_context/redaction.rb +8 -2
- data/lib/rails_ai_context/route_coverage.rb +5 -1
- data/lib/rails_ai_context/safe_file.rb +4 -1
- data/lib/rails_ai_context/safe_path.rb +61 -10
- data/lib/rails_ai_context/schema_hint.rb +8 -2
- data/lib/rails_ai_context/schema_version.rb +7 -9
- data/lib/rails_ai_context/serializers/claude_rules_serializer.rb +1 -1
- data/lib/rails_ai_context/serializers/markdown_serializer.rb +13 -4
- data/lib/rails_ai_context/serializers/section_facts.rb +5 -1
- data/lib/rails_ai_context/serializers/stack_overview_helper.rb +2 -2
- data/lib/rails_ai_context/serializers/tool_guide_helper.rb +7 -4
- data/lib/rails_ai_context/server.rb +26 -7
- data/lib/rails_ai_context/test_framework.rb +32 -5
- data/lib/rails_ai_context/tools/analyze_feature.rb +22 -10
- data/lib/rails_ai_context/tools/base_tool.rb +69 -30
- data/lib/rails_ai_context/tools/diagnose.rb +1 -1
- data/lib/rails_ai_context/tools/generate_test.rb +8 -9
- data/lib/rails_ai_context/tools/get_active_support.rb +19 -4
- data/lib/rails_ai_context/tools/get_api.rb +17 -11
- data/lib/rails_ai_context/tools/get_autoload.rb +10 -5
- data/lib/rails_ai_context/tools/get_callbacks.rb +65 -14
- data/lib/rails_ai_context/tools/get_component_catalog.rb +7 -4
- data/lib/rails_ai_context/tools/get_concern.rb +31 -18
- data/lib/rails_ai_context/tools/get_config.rb +52 -18
- data/lib/rails_ai_context/tools/get_controllers.rb +57 -13
- data/lib/rails_ai_context/tools/get_conventions.rb +44 -22
- data/lib/rails_ai_context/tools/get_engines.rb +2 -0
- data/lib/rails_ai_context/tools/get_env.rb +209 -52
- data/lib/rails_ai_context/tools/get_env_config.rb +38 -7
- data/lib/rails_ai_context/tools/get_frontend_stack.rb +56 -7
- data/lib/rails_ai_context/tools/get_helper_methods.rb +136 -26
- data/lib/rails_ai_context/tools/get_job_pattern.rb +143 -87
- data/lib/rails_ai_context/tools/get_mailers.rb +59 -2
- data/lib/rails_ai_context/tools/get_model_details.rb +178 -28
- data/lib/rails_ai_context/tools/get_partial_interface.rb +101 -62
- data/lib/rails_ai_context/tools/get_routes.rb +35 -9
- data/lib/rails_ai_context/tools/get_schema.rb +160 -61
- data/lib/rails_ai_context/tools/get_service_pattern.rb +121 -25
- data/lib/rails_ai_context/tools/get_stimulus.rb +1 -1
- data/lib/rails_ai_context/tools/get_test_info.rb +152 -82
- data/lib/rails_ai_context/tools/get_view.rb +37 -20
- data/lib/rails_ai_context/tools/migration_advisor.rb +64 -24
- data/lib/rails_ai_context/tools/onboard.rb +174 -88
- data/lib/rails_ai_context/tools/performance_check.rb +5 -13
- data/lib/rails_ai_context/tools/query.rb +122 -28
- data/lib/rails_ai_context/tools/read_logs.rb +45 -17
- data/lib/rails_ai_context/tools/runtime_info.rb +1 -1
- data/lib/rails_ai_context/tools/safe_call.rb +20 -15
- data/lib/rails_ai_context/tools/search_code.rb +114 -40
- data/lib/rails_ai_context/tools/search_docs.rb +22 -11
- data/lib/rails_ai_context/tools/security_scan.rb +51 -4
- data/lib/rails_ai_context/tools/validate.rb +21 -4
- data/lib/rails_ai_context/tools/validate_semantics.rb +18 -6
- data/lib/rails_ai_context/version.rb +1 -1
- data/lib/rails_ai_context/vfs.rb +2 -2
- data/lib/rails_ai_context/view_file.rb +53 -3
- data/lib/rails_ai_context.rb +10 -2
- data/server.json +1 -1
- metadata +34 -3
data/docs/TOOLS.md
CHANGED
|
@@ -111,7 +111,7 @@ Method-aware code extraction with surrounding class context. Every line returned
|
|
|
111
111
|
|
|
112
112
|
### `rails_analyze_feature`
|
|
113
113
|
|
|
114
|
-
Full-stack feature analysis: models + controllers + routes + services + jobs + views + tests in one call.
|
|
114
|
+
Full-stack feature analysis: models + controllers + routes + services + admin resources + jobs + views + tests in one call.
|
|
115
115
|
|
|
116
116
|
| Parameter | Type | Default | Description |
|
|
117
117
|
|:----------|:-----|:--------|:------------|
|
|
@@ -130,7 +130,12 @@ Composite context: schema + model + controller + routes + views for a resource.
|
|
|
130
130
|
|
|
131
131
|
### `rails_onboard`
|
|
132
132
|
|
|
133
|
-
Narrative app walkthrough for getting up to speed.
|
|
133
|
+
Narrative app walkthrough for getting up to speed. It ends with the app's custom
|
|
134
|
+
rake tasks (Rakefile, `lib/tasks`, `rakelib`), each with its arguments,
|
|
135
|
+
description and file: the first 15 at `standard`, every one at `full`.
|
|
136
|
+
Then the app's own generators under `lib/generators` (the `bin/rails generate`
|
|
137
|
+
command and its USAGE line), the `lib/templates` files that replace a built-in
|
|
138
|
+
generator's template, and the Railties under `lib/` with their initializers.
|
|
134
139
|
|
|
135
140
|
| Parameter | Type | Default | Description |
|
|
136
141
|
|:----------|:-----|:--------|:------------|
|
|
@@ -179,8 +184,17 @@ Concerns as `[UNAVAILABLE]` rather than dropped.
|
|
|
179
184
|
Callbacks grouped by type, in Rails event order, with source code. The list
|
|
180
185
|
covers concern-declared callbacks too, with the body read from the concern
|
|
181
186
|
file, and the "From Concerns" section says which concern declared each one.
|
|
182
|
-
Within one type the order is
|
|
183
|
-
its
|
|
187
|
+
Within one type the order is the order Rails runs them: base classes first, a
|
|
188
|
+
concern's callbacks where its `include` line stands, then the model's own, and
|
|
189
|
+
a `before_` or `around_` callback with `prepend: true` first. `after_commit`
|
|
190
|
+
and `after_rollback` are last declared first unless the app turns on
|
|
191
|
+
`run_after_transaction_callbacks_in_order_defined` (`load_defaults 7.1`). A
|
|
192
|
+
method declared again for one type shows once, with the later declaration's
|
|
193
|
+
conditions, as Rails keeps it. The order is read from source in both tiers,
|
|
194
|
+
and dynamic dispatch is not evaluated: a callback registered through
|
|
195
|
+
`send(:before_save, ...)`, through a method called with `send` or defined with
|
|
196
|
+
`define_singleton_method`, or through a module included in `class << self` is
|
|
197
|
+
not read, so the list can miss it or show the definition it replaces.
|
|
184
198
|
|
|
185
199
|
| Parameter | Type | Default | Description |
|
|
186
200
|
|:----------|:-----|:--------|:------------|
|
|
@@ -226,7 +240,9 @@ fully qualified controller key answers with its own routes only; a short name
|
|
|
226
240
|
reaches the controller whose trailing segments it spells, and not the ones that
|
|
227
241
|
merely start with it. Rack apps attached with `mount`
|
|
228
242
|
or `match ... to:` are named with the path they answer on, when the source
|
|
229
|
-
spells one out.
|
|
243
|
+
spells one out. A mounted Grape API lists its endpoints under the mount (verb,
|
|
244
|
+
full path, declared params), read from its classes in `app/api` and `lib/api`;
|
|
245
|
+
the summary counts them.
|
|
230
246
|
|
|
231
247
|
| Parameter | Type | Default | Description |
|
|
232
248
|
|:----------|:-----|:--------|:------------|
|
|
@@ -367,7 +383,7 @@ API layer: api_only mode, serialization strategy (Jbuilder, serializers), GraphQ
|
|
|
367
383
|
|
|
368
384
|
### `rails_get_conventions`
|
|
369
385
|
|
|
370
|
-
Auth checks, flash messages, create action template, test patterns.
|
|
386
|
+
Auth checks, flash messages, create action template, test patterns, and the admin resources ActiveAdmin, Administrate, Avo, Madmin and Trestle register (with ActiveAdmin's permitted params).
|
|
371
387
|
|
|
372
388
|
| Parameter | Type | Default | Description |
|
|
373
389
|
|:----------|:-----|:--------|:------------|
|
|
@@ -375,7 +391,7 @@ Auth checks, flash messages, create action template, test patterns.
|
|
|
375
391
|
|
|
376
392
|
### `rails_get_config`
|
|
377
393
|
|
|
378
|
-
Database config, auth framework, assets, cache, queue, Action Cable.
|
|
394
|
+
Database config, auth framework, assets, cache, queue, Action Cable. The `use` and `map` calls in `config.ru` are listed apart from the stack, since they run before Rails.
|
|
379
395
|
|
|
380
396
|
| Parameter | Type | Default | Description |
|
|
381
397
|
|:----------|:-----|:--------|:------------|
|
|
@@ -393,7 +409,7 @@ found under a load-order prefix too (`config/initializers/3_omniauth.rb`).
|
|
|
393
409
|
|
|
394
410
|
### `rails_get_env`
|
|
395
411
|
|
|
396
|
-
Environment variables + credentials keys (values are never exposed). Scans `.rb`, `.rake`, ERB views and config YAML under `app`, `config` and `lib
|
|
412
|
+
Environment variables + credentials keys (values are never exposed). Scans `.rb`, `.rake`, ERB views and config YAML under `app`, `config` and `lib`, plus `config.ru`, `db/seeds.rb`, `db/seeds/` and the Ruby scripts in `bin/`; files matching `sensitive_patterns` (`config/database.yml`, credentials, keys) are never read, and the answer says so. The env Kamal's `config/deploy.yml` gives the app container is listed too: secret names only, and clear values, except one holding a URL or a key-like token, or under a secret-named variable, which shows as hidden, and one an ERB tag sets, which says so. So are the setting keys in the config gem's `config/settings.yml` and `config/settings/<env>.yml` when the bundle has the config gem, and the attributes of each `Anyway::Config` class in `config/configs` or `app/configs` with the env name it reads (`PAYMENT_API_KEY`), never their values. A variable whose call sites pass different defaults is labelled as such rather than with one site's default; `detail:"full"` names each site's. A default is redacted by the rule a source literal gets: a credential format under any name, a URL's password, and under a secret-named variable a value that is not clearly something else, so an address, URL or hostname default prints as written.
|
|
397
413
|
|
|
398
414
|
| Parameter | Type | Default | Description |
|
|
399
415
|
|:----------|:-----|:--------|:------------|
|
|
@@ -409,7 +425,9 @@ Application and framework helpers with view cross-references.
|
|
|
409
425
|
|
|
410
426
|
### `rails_get_service_pattern`
|
|
411
427
|
|
|
412
|
-
Service object interface, dependencies, side effects, callers
|
|
428
|
+
Service object interface, dependencies, side effects, callers, read from
|
|
429
|
+
`app/services/`, `app/interactions/` and `app/interactors/`. An interactor
|
|
430
|
+
organizer lists the steps it runs, in order. An
|
|
413
431
|
ActiveInteraction's inputs include the ones it inherits, with the filters
|
|
414
432
|
nested inside a `hash` filter shown under it. Callers are read from every
|
|
415
433
|
`app/` and `lib/` tree, and on a booted app from any other directory it
|
|
@@ -437,7 +455,18 @@ job inherits from is not counted as a job; the listing names the ones it left
|
|
|
437
455
|
out, in the sentence the service and mailer listings use, and `job:` answers
|
|
438
456
|
for one of them with what every job below it inherits: its queue, options,
|
|
439
457
|
retries, mixins, throttle and callbacks, and which jobs inherit it. `job:`
|
|
440
|
-
answers a worker name as well as a job name.
|
|
458
|
+
answers a worker name as well as a job name. A job's own page shows its
|
|
459
|
+
`queue_with_priority`, `enqueue_after_transaction_commit` and Solid Queue
|
|
460
|
+
`limits_concurrency` (its own or the nearest base's), every `retry_on` option,
|
|
461
|
+
Sidekiq's `sidekiq_retry_in` and `sidekiq_retries_exhausted` blocks, and its
|
|
462
|
+
enqueue, perform and discard callbacks as written. A job that includes
|
|
463
|
+
`ActiveJob::Continuable` (itself or through a base) is marked continuable, with
|
|
464
|
+
the steps its `perform` runs in order, each a block or a method. A model method
|
|
465
|
+
delayed_job's `handle_asynchronously` wraps is listed as a background method with
|
|
466
|
+
its options, here and on the model's `rails_get_model_details` page. The listing names the queues
|
|
467
|
+
`config/sidekiq.yml` declares and the queues Solid Queue's workers poll in
|
|
468
|
+
`config/queue.yml` (this environment's section), and names each job queue no
|
|
469
|
+
worker polls; that job's page says so beside its queue.
|
|
441
470
|
|
|
442
471
|
| Parameter | Type | Default | Description |
|
|
443
472
|
|:----------|:-----|:--------|:------------|
|
|
@@ -467,6 +496,19 @@ I18n setup: default/available locales, backend, locale files with key counts, pe
|
|
|
467
496
|
|
|
468
497
|
ActionMailer mailers: every mailer class with its delivery actions and delivery method. A mailer that declares no action of its own - one taking them from a gem base or a mixin, or one called through class methods - is listed with where its actions come from rather than left out. A base other mailers inherit from is not one: it leaves the listing, and a line above names the ones left out. Asked for by name, a base answers with what every mailer below it inherits - its layout, helpers, defaults, callbacks and mixins as the app wrote them, the methods it defines - and which mailers inherit it.
|
|
469
498
|
|
|
499
|
+
Each mailer also shows its own `default`, `layout`, `helper` and action and
|
|
500
|
+
deliver callbacks as written, the template formats of each action under
|
|
501
|
+
`app/views/<mailer>`, and its preview class with the emails it previews (read
|
|
502
|
+
from `test/mailers/previews`, `spec/mailers/previews` and the
|
|
503
|
+
`action_mailer.preview_paths` the config adds). The full listing opens with the
|
|
504
|
+
queue `deliver_later` uses (ActionMailer's setting booted; the config's
|
|
505
|
+
statically, ActiveJob's default queue from `load_defaults` 6.1 on), the
|
|
506
|
+
interceptors and observers the config registers, and the preview paths.
|
|
507
|
+
|
|
508
|
+
The full listing ends with the Action Mailbox mailboxes: the `routing` rules in
|
|
509
|
+
the order Rails tries them, the mailbox each sends to (and whether `app/mailboxes`
|
|
510
|
+
defines it), and each mailbox's processing callbacks.
|
|
511
|
+
|
|
470
512
|
| Parameter | Type | Default | Description |
|
|
471
513
|
|:----------|:-----|:--------|:------------|
|
|
472
514
|
| `mailer` | string | - | One mailer, by exact name |
|
|
@@ -487,13 +529,17 @@ Autoloading setup: Zeitwerk vs Classic mode, autoloaders with collapsed/ignored
|
|
|
487
529
|
|
|
488
530
|
### `rails_get_active_support`
|
|
489
531
|
|
|
490
|
-
ActiveSupport surface: concerns registry, deprecators, MessageVerifier/MessageEncryptor usage, tagged logging, subscribed `on_load` hooks, cache store. Validator classes living among the concerns are counted and listed apart from them, as `rails_get_concern` does.
|
|
532
|
+
ActiveSupport surface: concerns registry, deprecators, MessageVerifier/MessageEncryptor usage, the `ActiveSupport::Notifications` events the app subscribes to (`subscribe`, `monotonic_subscribe`, and a Subscriber's `attach_to`) with file and line, read from source in both tiers, tagged logging, subscribed `on_load` hooks, cache store. Validator classes living among the concerns are counted and listed apart from them, as `rails_get_concern` does.
|
|
491
533
|
|
|
492
534
|
*No parameters.*
|
|
493
535
|
|
|
494
536
|
### `rails_get_env_config`
|
|
495
537
|
|
|
496
|
-
Per-environment configuration from `config/environments/*.rb`: notable toggles (`force_ssl`, `eager_load`, caching, log level, queue adapter, mailer delivery) and every config key each environment sets. A key assigned in more than one branch reports every value with its condition; booted, the running environment reports the value the app resolved.
|
|
538
|
+
Per-environment configuration from `config/environments/*.rb`: notable toggles (`force_ssl`, `eager_load`, caching, log level, queue adapter, mailer delivery) and every config key each environment sets. A key assigned in more than one branch reports every value with its condition; booted, the running environment reports the value the app resolved. An
|
|
539
|
+
"Every environment" block above them lists the keys `config/application.rb`
|
|
540
|
+
sets (`config.x` included) and, for each `config_for(:name)`, the keys
|
|
541
|
+
`config/name.yml` gives the running environment, `shared` merged in, names
|
|
542
|
+
only.
|
|
497
543
|
|
|
498
544
|
| Parameter | Type | Default | Description |
|
|
499
545
|
|:----------|:-----|:--------|:------------|
|
|
@@ -229,6 +229,7 @@ module RailsAiContext
|
|
|
229
229
|
"Frontend" => <<~SECTION
|
|
230
230
|
# ── Frontend Framework Detection ─────────────────────────────────
|
|
231
231
|
# Auto-detected from package.json, config/vite.json, etc. Override only if needed.
|
|
232
|
+
# A path outside the app root is read for package.json, lockfiles, bundler config and its tsconfig.json.
|
|
232
233
|
# config.frontend_paths = ["app/frontend", "../web-client"]
|
|
233
234
|
SECTION
|
|
234
235
|
}.freeze
|
|
@@ -10,11 +10,14 @@ module RailsAiContext
|
|
|
10
10
|
# excludes what the class skipped, so the parent's filters are separated by
|
|
11
11
|
# name rather than added.
|
|
12
12
|
module ActionFilters
|
|
13
|
-
|
|
13
|
+
# The ancestors' filters, the names they dropped, the conditions they set, every name they
|
|
14
|
+
# declare, and whether every class up to ActionController was read.
|
|
15
|
+
ParentWalk = Struct.new(:filters, :dropped, :conditions, :declares, :whole_chain)
|
|
14
16
|
|
|
15
17
|
module_function
|
|
16
18
|
|
|
17
|
-
# { own: [filter], inherited: [filter], skipped: [name] } for one action
|
|
19
|
+
# { own: [filter], inherited: [filter], chain: [filter], skipped: [name] } for one action:
|
|
20
|
+
# `chain` is own and inherited together, in the order Rails adds them; after filters run in reverse.
|
|
18
21
|
# `source:` is the controller's source when the caller already has it.
|
|
19
22
|
def for(ctx, controller_name, action, source: nil, root: nil)
|
|
20
23
|
split(ctx, controller_name, action.to_s, source: source, root: root)
|
|
@@ -37,15 +40,17 @@ module RailsAiContext
|
|
|
37
40
|
|
|
38
41
|
def split(ctx, controller_name, action, source:, root:)
|
|
39
42
|
info = Payload.controllers(ctx)[controller_name.to_s]
|
|
40
|
-
return { own: [], inherited: [], skipped: [] } unless info.is_a?(Hash)
|
|
43
|
+
return { own: [], inherited: [], chain: [], skipped: [] } unless info.is_a?(Hash)
|
|
41
44
|
|
|
42
45
|
skips = own_skips(ctx, controller_name, info, action, root: root, source: source)
|
|
43
46
|
skipped = absolute_names(skips, action)
|
|
44
47
|
# A skip record states what does not run, so it is never a filter.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
offsets = Hash.new(0)
|
|
49
|
+
declared = number_blocks(Array(info[:filters]).grep(Hash).reject { |f| f[:skipped] }, offsets)
|
|
50
|
+
walk = parent_filters(ctx, info[:parent_class], action, skipped, root: root, within: controller_name.to_s, offsets: offsets)
|
|
51
|
+
parent = walk.filters
|
|
52
|
+
dropped = walk.dropped
|
|
53
|
+
conditions = merge_conditions(walk.conditions, conditions_by_name(skips, action))
|
|
49
54
|
# The runtime tier's list is the whole chain, so an ancestor's skip
|
|
50
55
|
# applies here too - except to what this body declares again, which
|
|
51
56
|
# Rails re-adds.
|
|
@@ -73,16 +78,71 @@ module RailsAiContext
|
|
|
73
78
|
runs_once?(f[:from_concern], root, controller_name))
|
|
74
79
|
end
|
|
75
80
|
own = mark_conditional_skips(applicable.reject(&inherited_here), conditions, action)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
81
|
+
# The runtime tier's list is the whole chain: once the body, every ancestor and every module
|
|
82
|
+
# they mix in were read, a name none of them declares was installed from outside it (a gem's on_load).
|
|
83
|
+
outside = []
|
|
84
|
+
whole_chain = walk.whole_chain && info[:concerns_unread].blank?
|
|
85
|
+
if whole_chain && Array(info[:filters]).grep(Hash).any? { |f| f[:declared] || f[:skipped] }
|
|
86
|
+
outside, own = own.partition { |f| !f[:declared] && !f[:from_concern] }
|
|
87
|
+
outside = outside.map { |f| f.merge(provenance: "not declared in the controller chain") }
|
|
88
|
+
end
|
|
89
|
+
moved = applicable.select(&inherited_here)
|
|
90
|
+
.map { |f| f.except(:from, :from_concern, :provenance).merge(attribution_of[entry_key(f)]) }
|
|
91
|
+
.group_by { |f| entry_key(f) }
|
|
92
|
+
# Each moved entry takes its ancestor's slot, so one the runtime list does not carry keeps its place among them.
|
|
93
|
+
kept = parent.flat_map { |f| declared_names.include?(entry_key(f)) ? Array(moved.delete(entry_key(f))) : [ f ] }
|
|
94
|
+
inherited = mark_conditional_skips(kept + moved.values.flatten, conditions, action) + outside
|
|
95
|
+
own = unnumbered(own)
|
|
96
|
+
inherited = unnumbered(inherited)
|
|
97
|
+
inherited += unplaced_conditional_skips(own + inherited, conditions, action,
|
|
98
|
+
walk.declares | declared.map { |f| f[:name].to_s }.to_set)
|
|
99
|
+
chain = run_sequence(inherited + own, info)
|
|
100
|
+
mine = own.to_set.compare_by_identity
|
|
101
|
+
|
|
102
|
+
{ own: chain.select { |f| mine.include?(f) }, inherited: chain.reject { |f| mine.include?(f) },
|
|
103
|
+
chain: chain, skipped: skipped }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# The runtime tier's list is the reflection list, already in run order; a filter it
|
|
107
|
+
# does not carry keeps its declared place, just after the carried filter declared before it.
|
|
108
|
+
# The static tier's lists are declarations, which Rails appends in turn and a prepend_* macro unshifts.
|
|
109
|
+
def run_sequence(filters, info)
|
|
110
|
+
declared = filters.each_with_object([]) { |f, list| f[:prepend] ? list.unshift(f) : list.push(f) }
|
|
111
|
+
return declared if RailsAiContext.static_tier?
|
|
112
|
+
|
|
113
|
+
rank = {}
|
|
114
|
+
Array(info[:filters]).grep(Hash).reject { |f| f[:skipped] }.each_with_index { |f, i| rank[entry_key(f)] ||= i }
|
|
115
|
+
leading = []
|
|
116
|
+
behind = {}.compare_by_identity
|
|
117
|
+
last = nil
|
|
118
|
+
declared.each do |f|
|
|
119
|
+
next (last ? behind[last] : leading) << f unless rank.key?(entry_key(f))
|
|
120
|
+
|
|
121
|
+
behind[f] = []
|
|
122
|
+
last = f
|
|
123
|
+
end
|
|
124
|
+
leading + behind.keys.each_with_index.sort_by { |f, i| [ rank[entry_key(f)], i ] }.flat_map { |f, _| [ f ] + behind[f] }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
BLOCK_NUMBER = /#\d+\z/
|
|
128
|
+
|
|
129
|
+
# Numbered from the chain's end so one block reads the same in a class's list and every descendant's.
|
|
130
|
+
def number_blocks(filters, offsets)
|
|
131
|
+
seen = Hash.new(0)
|
|
132
|
+
numbered = filters.reverse.map do |filter|
|
|
133
|
+
next filter unless Introspectors::ControllerFilters.block?(filter[:name])
|
|
134
|
+
|
|
135
|
+
key = entry_key(filter)
|
|
136
|
+
number = offsets[key] + seen[key]
|
|
137
|
+
seen[key] += 1
|
|
138
|
+
filter.merge(name: "#{filter[:name]}##{number}")
|
|
139
|
+
end.reverse
|
|
140
|
+
filters.each { |filter| offsets[entry_key(filter)] += 1 if filter[:declared] && Introspectors::ControllerFilters.block?(filter[:name]) }
|
|
141
|
+
numbered
|
|
142
|
+
end
|
|
81
143
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
declares | declared.map { |f| f[:name].to_s }.to_set),
|
|
85
|
-
skipped: skipped }
|
|
144
|
+
def unnumbered(filters)
|
|
145
|
+
filters.map { |filter| filter[:name].to_s.match?(BLOCK_NUMBER) ? filter.merge(name: filter[:name].to_s.sub(BLOCK_NUMBER, "")) : filter }
|
|
86
146
|
end
|
|
87
147
|
|
|
88
148
|
# A skip of a name no ancestor in the payload declares is the only
|
|
@@ -98,7 +158,7 @@ module RailsAiContext
|
|
|
98
158
|
# A conditional skip never removes the filter; the record carries the
|
|
99
159
|
# condition. See CONTEXT.md, "Filter chain".
|
|
100
160
|
def conditional?(skip, action)
|
|
101
|
-
!skip[:if].nil? || !skip[:unless].nil? || partial?(skip, action)
|
|
161
|
+
!skip[:if].nil? || !skip[:unless].nil? || !skip[:condition].nil? || partial?(skip, action)
|
|
102
162
|
end
|
|
103
163
|
|
|
104
164
|
# Partial only where the answer covers every action: a per-action answer
|
|
@@ -139,6 +199,7 @@ module RailsAiContext
|
|
|
139
199
|
|
|
140
200
|
tail[:skipped_if] = condition_text(skip[:if]) if skip[:if]
|
|
141
201
|
tail[:skipped_unless] = condition_text(skip[:unless]) if skip[:unless]
|
|
202
|
+
tail[:skipped_condition] = skip[:condition] if skip[:condition]
|
|
142
203
|
return tail unless partial?(skip, action)
|
|
143
204
|
|
|
144
205
|
tail[:skipped_on] = action_names(skip[:only]) if Array(skip[:only]).any?
|
|
@@ -181,7 +242,7 @@ module RailsAiContext
|
|
|
181
242
|
# ends it: reconstructing a path from a class name breaks on every app
|
|
182
243
|
# inflection. A bare superclass is resolved against the enclosing namespace
|
|
183
244
|
# first, the way Ruby does.
|
|
184
|
-
def parent_filters(ctx, parent_class, action, skipped, root:, within: nil)
|
|
245
|
+
def parent_filters(ctx, parent_class, action, skipped, root:, within: nil, offsets: Hash.new(0))
|
|
185
246
|
controllers = Payload.controllers(ctx)
|
|
186
247
|
seen = Set.new
|
|
187
248
|
found = {}
|
|
@@ -194,18 +255,30 @@ module RailsAiContext
|
|
|
194
255
|
evidence = {}
|
|
195
256
|
depth = 0
|
|
196
257
|
dropped = skipped.map(&:to_s).to_set
|
|
258
|
+
# Whole only when every class up to ActionController was read: a walk that
|
|
259
|
+
# stops early may have missed the class that declares a name.
|
|
260
|
+
whole = true
|
|
261
|
+
reached = false
|
|
197
262
|
name = Introspectors::ActionResolver.resolve_entry_name(controllers, parent_class, within)
|
|
198
263
|
|
|
199
264
|
while name && !seen.include?(name)
|
|
200
265
|
seen << name
|
|
201
266
|
depth += 1
|
|
202
267
|
info = controllers[name]
|
|
203
|
-
|
|
204
|
-
|
|
268
|
+
path = info.is_a?(Hash) ? nil : base_controller_path(name, root)
|
|
269
|
+
source = path && SafeFile.read(path)
|
|
270
|
+
if source
|
|
271
|
+
info ||= { filters: base_filters(source, name, root), file: PortablePath.relativize(path, (root || default_root).to_s),
|
|
272
|
+
parent_class: Introspectors::DeclaredConstant.parent_declaration(source, name.to_s)&.superclass }
|
|
273
|
+
end
|
|
205
274
|
unless info.is_a?(Hash)
|
|
275
|
+
framework = name.to_s.delete_prefix("::").start_with?("ActionController::")
|
|
276
|
+
reached ||= framework
|
|
277
|
+
whole &&= framework
|
|
206
278
|
name = gem_controller_base(name, root)
|
|
207
279
|
next
|
|
208
280
|
end
|
|
281
|
+
whole = false if info[:error]
|
|
209
282
|
|
|
210
283
|
# The class that skips a filter must not contribute it either: in the
|
|
211
284
|
# booted tier its own list is the reflection list, which carries every
|
|
@@ -215,7 +288,9 @@ module RailsAiContext
|
|
|
215
288
|
# The walk runs closest ancestor first, so a nearer class's condition
|
|
216
289
|
# is the one the child inherits.
|
|
217
290
|
conditions = merge_conditions(conditions_by_name(skips, action), conditions)
|
|
218
|
-
|
|
291
|
+
# The child's chain names this class's own blocks by the file they are in.
|
|
292
|
+
carried = number_blocks(Introspectors::ControllerFilters.in_file(Array(info[:filters]).grep(Hash).reject { |f| f[:skipped] }, info[:file]),
|
|
293
|
+
offsets)
|
|
219
294
|
# Counted before the rejects below, so a conditional skip of a name
|
|
220
295
|
# the walk did see is never mistaken for a skip of a declaration it
|
|
221
296
|
# could not. A skip record is not a sighting, hence the reject above.
|
|
@@ -233,7 +308,7 @@ module RailsAiContext
|
|
|
233
308
|
name = Introspectors::ActionResolver.resolve_entry_name(controllers, info[:parent_class], name)
|
|
234
309
|
end
|
|
235
310
|
|
|
236
|
-
|
|
311
|
+
ParentWalk.new(run_order(found, attributed, positions, evidence), dropped, conditions, declares, whole && reached)
|
|
237
312
|
end
|
|
238
313
|
|
|
239
314
|
# The closest ancestor carrying a filter keeps its constraints, but a
|
|
@@ -266,7 +341,7 @@ module RailsAiContext
|
|
|
266
341
|
.sort_by { |key, index| [ -positions.fetch(key, 0), index ] }
|
|
267
342
|
.map do |key, _|
|
|
268
343
|
entry = found[key]
|
|
269
|
-
next entry if attributed.include?(key) || !evidence[key]
|
|
344
|
+
next entry if attributed.include?(key) || !evidence[key] || entry[:from_concern]
|
|
270
345
|
|
|
271
346
|
entry.merge(from: nil, provenance: "not declared in the controller chain").compact
|
|
272
347
|
end
|
|
@@ -313,7 +388,8 @@ module RailsAiContext
|
|
|
313
388
|
def skip_flag_records(info, action)
|
|
314
389
|
last_records(info, action).select { |_, f| f[:skipped] }
|
|
315
390
|
.map do |name, f|
|
|
316
|
-
{ name: name, kind: f[:kind], if: f[:if], unless: f[:unless], only: f[:only], except: f[:except]
|
|
391
|
+
{ name: name, kind: f[:kind], if: f[:if], unless: f[:unless], only: f[:only], except: f[:except],
|
|
392
|
+
condition: f[:condition] }
|
|
317
393
|
end
|
|
318
394
|
end
|
|
319
395
|
|
|
@@ -344,17 +420,9 @@ module RailsAiContext
|
|
|
344
420
|
source ||= carried_source(ctx, controller_name, root)
|
|
345
421
|
return [] unless source
|
|
346
422
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
kind = call[:name].to_s.sub(/\Askip_/, "").sub(/_action\z/, "")
|
|
352
|
-
Array(call[:arguments]).select { |a| a.is_a?(Symbol) || a.is_a?(String) }
|
|
353
|
-
.map do |a|
|
|
354
|
-
{ name: a.to_s, kind: kind, if: options[:if], unless: options[:unless],
|
|
355
|
-
only: options[:only], except: options[:except] }
|
|
356
|
-
end
|
|
357
|
-
end.uniq { |skip| skip[:name] }
|
|
423
|
+
skip_records(source).select { |skip| applies?(skip, action) }
|
|
424
|
+
.map { |skip| skip.slice(:name, :kind, :if, :unless, :only, :except, :condition) }
|
|
425
|
+
.uniq { |skip| skip[:name] }
|
|
358
426
|
rescue => e
|
|
359
427
|
RailsAiContext.debug_fail(e, [], label: "ActionFilters skip_source_records")
|
|
360
428
|
end
|
|
@@ -365,13 +433,16 @@ module RailsAiContext
|
|
|
365
433
|
# printed it. Only a name the app has a file for is read,
|
|
366
434
|
# so a gem-owned parent, or one an inflection renames, still ends the walk.
|
|
367
435
|
def base_controller_source(name, root)
|
|
436
|
+
path = base_controller_path(name, root)
|
|
437
|
+
path && SafeFile.read(path)
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def base_controller_path(name, root)
|
|
368
441
|
root ||= default_root
|
|
369
442
|
return nil unless root
|
|
370
443
|
|
|
371
444
|
relative = "#{name.to_s.underscore}.rb"
|
|
372
|
-
|
|
373
|
-
.find { |candidate| File.exist?(candidate) }
|
|
374
|
-
path && SafeFile.read(path)
|
|
445
|
+
PathResolver.controller_dirs(root.to_s).map { |dir| File.join(dir, relative) }.find { |candidate| File.exist?(candidate) }
|
|
375
446
|
end
|
|
376
447
|
|
|
377
448
|
# Gem controllers whose base class is set in the app's initializers:
|
|
@@ -401,11 +472,10 @@ module RailsAiContext
|
|
|
401
472
|
# A module whose file is not found is taken for a concern.
|
|
402
473
|
def runs_once?(name, root, within)
|
|
403
474
|
root ||= default_root
|
|
404
|
-
|
|
405
|
-
source = path && SafeFile.read(path)
|
|
475
|
+
source = root && ConcernPaths.module_source(root.to_s, name, prefer: "controller", within: within.to_s)
|
|
406
476
|
return true unless source
|
|
407
477
|
|
|
408
|
-
RunCache.fetch([ :concern_module,
|
|
478
|
+
RunCache.fetch([ :concern_module, source ]) do
|
|
409
479
|
Introspectors::SourceIntrospector.walk_source(source, { mixins: Introspectors::Listeners::MixinsListener })[:mixins]
|
|
410
480
|
.any? { |mixin| mixin[:macro] == :extend && mixin[:name] == "ActiveSupport::Concern" }
|
|
411
481
|
end
|
|
@@ -413,7 +483,7 @@ module RailsAiContext
|
|
|
413
483
|
|
|
414
484
|
# The last value the initializers set; :computed when it is not a string literal.
|
|
415
485
|
def configured_base(root, setting)
|
|
416
|
-
|
|
486
|
+
PathResolver.initializer_paths(root).filter_map do |path|
|
|
417
487
|
source = SafeFile.read(path)
|
|
418
488
|
next unless source&.include?(setting.delete_suffix("="))
|
|
419
489
|
|
|
@@ -443,18 +513,21 @@ module RailsAiContext
|
|
|
443
513
|
SafeFile.read(File.join(root.to_s, file))
|
|
444
514
|
end
|
|
445
515
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
516
|
+
# The reader the listing uses, so a skip carries the same only:/except:/if:/unless: and the
|
|
517
|
+
# if/unless around it; one inside a `def` runs only when called, which ControllerFilters reads.
|
|
518
|
+
def skip_records(source)
|
|
519
|
+
RunCache.fetch([ :skip_records, source ]) do
|
|
520
|
+
Introspectors::ControllerFilters.from_source(source).select { |f| f[:skipped] }
|
|
521
|
+
end
|
|
450
522
|
end
|
|
451
523
|
|
|
452
524
|
private_class_method :default_root, :split, :applies?, :parent_filters, :skip_source_records, :carried_source,
|
|
453
525
|
:entry_key,
|
|
454
526
|
|
|
455
|
-
:
|
|
527
|
+
:skip_records, :base_filters, :skip_flag_records, :redeclared_names, :last_records, :own_skips,
|
|
456
528
|
:record_attribution, :conditional?, :partial?, :absolute_names, :conditions_by_name,
|
|
457
529
|
:merge_conditions, :mark_conditional_skips, :skip_tail, :action_names, :condition_text,
|
|
458
|
-
:unplaced_conditional_skips, :evidence_skips, :
|
|
530
|
+
:unplaced_conditional_skips, :evidence_skips, :configured_base, :runs_once?, :run_sequence,
|
|
531
|
+
:base_controller_path, :number_blocks, :unnumbered
|
|
459
532
|
end
|
|
460
533
|
end
|
|
@@ -13,14 +13,14 @@ module RailsAiContext
|
|
|
13
13
|
"bin/rails server" if File.exist?(File.join(root.to_s, "bin", "rails"))
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
# Builds the database from the
|
|
16
|
+
# Builds the database from the dump the app keeps, under whatever name schema_dump gives it, else its migrations.
|
|
17
17
|
# db:setup is not named: it also runs the seeds.
|
|
18
18
|
#
|
|
19
19
|
# @return [String, nil] nil for an app with no database config or bin/rails
|
|
20
20
|
def setup(root)
|
|
21
21
|
root = root.to_s
|
|
22
22
|
return nil unless File.exist?(File.join(root, "bin", "rails")) && File.exist?(File.join(root, "config", "database.yml"))
|
|
23
|
-
return "bin/rails db:create db:schema:load" if
|
|
23
|
+
return "bin/rails db:create db:schema:load" if Introspectors::SchemaDumpPath.present(root)
|
|
24
24
|
return "bin/rails db:create db:migrate" if Dir.exist?(File.join(root, "db", "migrate"))
|
|
25
25
|
|
|
26
26
|
"bin/rails db:create"
|
|
@@ -7,16 +7,73 @@ module RailsAiContext
|
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
9
|
def mongoid?(root)
|
|
10
|
-
root
|
|
11
|
-
|
|
10
|
+
File.exist?(File.join(root.to_s, "config", "mongoid.yml")) || uses_gem?(root, "mongoid")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def sequel?(root)
|
|
14
|
+
uses_gem?(root, "sequel-rails", "sequel")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def uses_gem?(root, *names)
|
|
18
|
+
lock = RailsAiContext::GemLock.for(root.to_s)
|
|
19
|
+
return names.any? { |name| lock.present?(name) } unless lock.missing?
|
|
20
|
+
|
|
21
|
+
# No lockfile yet (fresh checkout, bare directory): the Gemfile's own declaration.
|
|
22
|
+
RailsAiContext::Introspectors::GemfileGems.names(root.to_s).intersect?(names)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
ACTIVE_RECORD_REQUIRES = %w[rails/all active_record/railtie active_record].freeze
|
|
26
|
+
|
|
27
|
+
# False only when config/application.rb picks its frameworks railtie by
|
|
28
|
+
# railtie and leaves Active Record's out (a require, or a list it requires
|
|
29
|
+
# in a loop) and never configures it; a file it cannot read is taken to load it.
|
|
30
|
+
def active_record?(root)
|
|
31
|
+
source = RailsAiContext::SafeFile.read(File.join(root.to_s, "config", "application.rb"))
|
|
32
|
+
tree = source && RailsAiContext::AstCache.parse_string(source)
|
|
33
|
+
return true unless tree && tree.errors.empty?
|
|
34
|
+
|
|
35
|
+
nodes = Introspectors::AstWalk.each(tree.value).to_a
|
|
36
|
+
strings = nodes.grep(Prism::StringNode).map(&:unescaped)
|
|
37
|
+
return true if strings.intersect?(ACTIVE_RECORD_REQUIRES) || nodes.grep(Prism::CallNode).any? { |n| n.name == :active_record }
|
|
38
|
+
|
|
39
|
+
strings.none? { |s| s.end_with?("/railtie", "/engine") }
|
|
40
|
+
rescue StandardError, ScriptError
|
|
41
|
+
true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Why Active Record introspection does not apply here, or nil when it does.
|
|
45
|
+
def without_active_record(root)
|
|
46
|
+
return nil if active_record?(root)
|
|
47
|
+
|
|
48
|
+
return "this app uses Mongoid" if mongoid?(root)
|
|
49
|
+
return "this app uses Sequel" if sequel?(root)
|
|
50
|
+
|
|
51
|
+
"this app does not load Active Record"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Why the schema files Active Record would read are not its own: sequel-rails keeps its
|
|
55
|
+
# schema.rb and migrations in the same places, written in Sequel's DSL. Nil when they are.
|
|
56
|
+
def sequel_schema(root)
|
|
57
|
+
return nil unless sequel?(root)
|
|
58
|
+
|
|
59
|
+
format, dump = Introspectors::SchemaDumpPath.present(root)
|
|
60
|
+
return nil if format == :sql
|
|
61
|
+
|
|
62
|
+
file = dump || Introspectors::MigrationReplay.migration_files(Introspectors::MigrationReplay.migration_dirs(root), root: root).first
|
|
63
|
+
return nil unless file && sequel_migration?(RailsAiContext::SafeFile.read(file))
|
|
64
|
+
|
|
65
|
+
"#{file.delete_prefix("#{root.to_s.chomp('/')}/")} is a Sequel migration"
|
|
66
|
+
end
|
|
12
67
|
|
|
13
|
-
|
|
14
|
-
|
|
68
|
+
def sequel_migration?(source)
|
|
69
|
+
tree = source && RailsAiContext::AstCache.parse_string(source)
|
|
70
|
+
return false unless tree&.value
|
|
15
71
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
72
|
+
tree.value.statements.body.any? do |node|
|
|
73
|
+
node.is_a?(Prism::CallNode) && node.name == :migration &&
|
|
74
|
+
(node.receiver.is_a?(Prism::ConstantReadNode) || node.receiver.is_a?(Prism::ConstantPathNode)) &&
|
|
75
|
+
node.receiver.slice.delete_prefix("::") == "Sequel"
|
|
76
|
+
end
|
|
20
77
|
end
|
|
21
78
|
|
|
22
79
|
# The development database config/mongoid.yml names, by `database:` or the
|