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
|
@@ -40,11 +40,12 @@ module RailsAiContext
|
|
|
40
40
|
# @param lookup [#call, nil] constant name -> that class's source
|
|
41
41
|
# @param only [String, nil] answer for this class name alone, ignoring the file's other
|
|
42
42
|
# declarations (a concern that nests a validator class declares both)
|
|
43
|
+
# @param path_name [String, nil] the constant the source's path spells, so its Class.new class counts
|
|
43
44
|
# @return [Array<Link>]
|
|
44
|
-
def to(source, bases:, lookup: nil, seen: [], only: nil)
|
|
45
|
+
def to(source, bases:, lookup: nil, seen: [], only: nil, path_name: only)
|
|
45
46
|
return [] if source.nil? || seen.size >= MAX_DEPTH
|
|
46
47
|
|
|
47
|
-
declarations = DeclaredConstant.declarations(source)
|
|
48
|
+
declarations = DeclaredConstant.declarations(source, path_name: path_name)
|
|
48
49
|
declarations = [ DeclaredConstant.declaration_named(declarations, only) ].compact if only
|
|
49
50
|
return [] if declarations.empty?
|
|
50
51
|
|
|
@@ -58,7 +59,7 @@ module RailsAiContext
|
|
|
58
59
|
parent_source = lookup&.call(parent)
|
|
59
60
|
next if parent_source.nil?
|
|
60
61
|
|
|
61
|
-
rest = to(parent_source, bases: bases, lookup: lookup, seen: seen + [ parent ])
|
|
62
|
+
rest = to(parent_source, bases: bases, lookup: lookup, seen: seen + [ parent ], path_name: parent)
|
|
62
63
|
return [ Link.new(name: candidate.name, superclass: parent, source: source) ] + rest if rest.any?
|
|
63
64
|
end
|
|
64
65
|
|
|
@@ -95,21 +96,13 @@ module RailsAiContext
|
|
|
95
96
|
name == base || name.end_with?("::#{base}")
|
|
96
97
|
end
|
|
97
98
|
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
|
|
101
|
-
#
|
|
102
|
-
# @param declared [String] the subclass's fully qualified name
|
|
103
|
-
# @param parent [String, nil] the superclass as the source writes it
|
|
104
|
-
# @yieldparam candidate [String] a name to try, nearest scope first
|
|
105
|
-
# @return [Object, nil] the block's first truthy answer
|
|
106
|
-
def resolve_in_scope(declared, parent)
|
|
99
|
+
# A bare superclass resolves through Module.nesting: `module Fasp; class W < BaseWorker` tries Fasp::BaseWorker first.
|
|
100
|
+
# A nil `nesting` is read off the declared name, which is right for the nested form only.
|
|
101
|
+
def resolve_in_scope(declared, parent, nesting: nil)
|
|
107
102
|
parent = parent.to_s.delete_prefix("::")
|
|
108
103
|
return nil if parent.empty?
|
|
109
104
|
|
|
110
|
-
|
|
111
|
-
scope.size.downto(0) do |i|
|
|
112
|
-
candidate = (scope.first(i) + [ parent ]).join("::")
|
|
105
|
+
((nesting || nesting_of(declared)).map { |scope| "#{scope}::#{parent}" } + [ parent ]).each do |candidate|
|
|
113
106
|
# `class CostQuery::Export < Export` names the top-level Export; the
|
|
114
107
|
# nearest candidate is the class itself, which is nobody's parent.
|
|
115
108
|
next if candidate == declared.to_s
|
|
@@ -120,6 +113,12 @@ module RailsAiContext
|
|
|
120
113
|
nil
|
|
121
114
|
end
|
|
122
115
|
|
|
116
|
+
# The nesting the nested form gives a class: every enclosing name, innermost first.
|
|
117
|
+
def nesting_of(declared)
|
|
118
|
+
scope = declared.to_s.split("::")[0..-2]
|
|
119
|
+
scope.size.downto(1).map { |i| scope.first(i).join("::") }
|
|
120
|
+
end
|
|
121
|
+
|
|
123
122
|
# A callable from a constant name to the source of the file declaring
|
|
124
123
|
# it, probed against the app's autoload roots rather than a walk over
|
|
125
124
|
# the tree: Zeitwerk resolves a constant to one path under one root, and
|
|
@@ -13,23 +13,18 @@ module RailsAiContext
|
|
|
13
13
|
# namespace and the superclass chain is the caller's business, because only
|
|
14
14
|
# it knows the other files.
|
|
15
15
|
#
|
|
16
|
-
# It reads table_name, table_name_prefix
|
|
17
|
-
#
|
|
18
|
-
# static tier, and answering one of those and not the other would be worse
|
|
19
|
-
# than answering neither.
|
|
16
|
+
# It reads table_name, table_name_prefix, table_name_suffix,
|
|
17
|
+
# pluralize_table_names and a literal `self.primary_key =`.
|
|
20
18
|
module TableName
|
|
21
19
|
module_function
|
|
22
20
|
|
|
23
|
-
NONE = { table_name: nil, table_name_prefix: nil, table_name_suffix: nil }.freeze
|
|
24
|
-
|
|
25
|
-
PREFIX_INDEX = Concurrent::Map.new
|
|
26
|
-
APP_AFFIXES = Concurrent::Map.new
|
|
21
|
+
NONE = { table_name: nil, table_name_prefix: nil, table_name_suffix: nil, pluralize_table_names: nil, primary_key: nil }.freeze
|
|
27
22
|
|
|
28
23
|
# {"RssPolling" => "rss_polling_"}: the engine prefix no model file says.
|
|
29
24
|
# ponytail: reads `lib/**/engine.rb` only; walk lib wholesale if an app needs more.
|
|
30
25
|
def namespace_prefixes(root)
|
|
31
26
|
root = File.expand_path(root.to_s)
|
|
32
|
-
|
|
27
|
+
RunCache.fetch([ :namespace_prefixes, root ]) { read_namespace_prefixes(root) }
|
|
33
28
|
end
|
|
34
29
|
|
|
35
30
|
# A table an option writes as that source (a habtm join_table), read the
|
|
@@ -39,31 +34,18 @@ module RailsAiContext
|
|
|
39
34
|
statements&.size == 1 ? affixed_node(statements.first, own, root) : nil
|
|
40
35
|
end
|
|
41
36
|
|
|
42
|
-
|
|
43
|
-
PREFIX_INDEX.clear
|
|
44
|
-
APP_AFFIXES.clear
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# {table_name_prefix: "op_"}: what config/application.rb sets on
|
|
48
|
-
# config.active_record, the class attribute's value in every model.
|
|
49
|
-
def app_affixes(root)
|
|
50
|
-
return {} unless root
|
|
51
|
-
|
|
52
|
-
root = File.expand_path(root.to_s)
|
|
53
|
-
APP_AFFIXES.compute_if_absent(root) { read_app_affixes(root) }
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# All three declarations of one class body, read in one walk.
|
|
37
|
+
# Every declaration of one class body, read in one walk.
|
|
57
38
|
#
|
|
58
39
|
# @param source [String] the file's source
|
|
59
40
|
# @param name [String] the qualified name of the class or module
|
|
60
|
-
# @return [Hash] the
|
|
41
|
+
# @return [Hash] the five, each nil when this scope declares none
|
|
61
42
|
# @param root [String, nil] the app, whose configured affixes a table
|
|
62
43
|
# name interpolating table_name_prefix or table_name_suffix reads
|
|
63
44
|
def declarations(source, name, root = nil)
|
|
64
45
|
read(source, name) do |body|
|
|
65
46
|
own = { table_name_prefix: affix(body, :table_name_prefix), table_name_suffix: affix(body, :table_name_suffix) }
|
|
66
47
|
{ table_name: assigned(body, :table_name=) || interpolated(body, own, root) }.merge(own)
|
|
48
|
+
.merge(pluralize_table_names: boolean_assigned(body, :pluralize_table_names=), primary_key: primary_key_assigned(body))
|
|
67
49
|
end || NONE
|
|
68
50
|
end
|
|
69
51
|
|
|
@@ -77,8 +59,9 @@ module RailsAiContext
|
|
|
77
59
|
# name already carries that inflection - Zeitwerk resolved the constant
|
|
78
60
|
# from it. Underscoring the constant instead turns OAuthClientConfig into
|
|
79
61
|
# o_auth_client_configs, a table no app has.
|
|
80
|
-
def stem(path)
|
|
81
|
-
File.basename(path.to_s, ".rb")
|
|
62
|
+
def stem(path, pluralize = true)
|
|
63
|
+
base = File.basename(path.to_s, ".rb")
|
|
64
|
+
pluralize ? base.pluralize : base
|
|
82
65
|
end
|
|
83
66
|
|
|
84
67
|
# The table a model name alone implies. The namespace is not part of it:
|
|
@@ -141,21 +124,6 @@ module RailsAiContext
|
|
|
141
124
|
RailsAiContext.debug_fail(e, {}, label: "namespace_prefixes")
|
|
142
125
|
end
|
|
143
126
|
|
|
144
|
-
def read_app_affixes(root)
|
|
145
|
-
path = File.join(root, "config", "application.rb")
|
|
146
|
-
return {} unless File.file?(path)
|
|
147
|
-
|
|
148
|
-
Array(SourceIntrospector.walk(path, { config: Listeners::ConfigAssignmentListener })[:config])
|
|
149
|
-
.each_with_object({}) do |entry, found|
|
|
150
|
-
path = entry[:path]
|
|
151
|
-
next unless path.size == 2 && path.first == :active_record && entry[:value].is_a?(String)
|
|
152
|
-
|
|
153
|
-
found[path.last] = entry[:value] if %i[table_name_prefix table_name_suffix].include?(path.last)
|
|
154
|
-
end
|
|
155
|
-
rescue StandardError, ScriptError => e
|
|
156
|
-
RailsAiContext.debug_fail(e, {}, label: "app_affixes")
|
|
157
|
-
end
|
|
158
|
-
|
|
159
127
|
def engine_files(root)
|
|
160
128
|
roots = [ root ] + PathResolver.code_roots(root)
|
|
161
129
|
roots.flat_map { |dir| Dir.glob(File.join(dir, "lib", "**", "engine.rb")) }.uniq.sort
|
|
@@ -207,11 +175,24 @@ module RailsAiContext
|
|
|
207
175
|
return statements(node) if qualified == name
|
|
208
176
|
|
|
209
177
|
descend(node, scope + [ segment(node) ], name)
|
|
178
|
+
when Prism::ConstantWriteNode, Prism::ConstantPathWriteNode
|
|
179
|
+
written = node.is_a?(Prism::ConstantWriteNode) ? node.name.to_s : node.target.slice.delete_prefix("::")
|
|
180
|
+
return built_body(node.value) if (scope + [ written ]).join("::") == name
|
|
181
|
+
|
|
182
|
+
descend(node, scope, name)
|
|
210
183
|
else
|
|
211
184
|
descend(node, scope, name)
|
|
212
185
|
end
|
|
213
186
|
end
|
|
214
187
|
|
|
188
|
+
# `Name = Class.new(Base) do ... end` runs the block as Name's body.
|
|
189
|
+
def built_body(value)
|
|
190
|
+
return [] unless value.is_a?(Prism::CallNode) && value.name == :new && value.receiver&.slice == "Class"
|
|
191
|
+
|
|
192
|
+
block = value.block
|
|
193
|
+
block.is_a?(Prism::BlockNode) && block.body.is_a?(Prism::StatementsNode) ? block.body.body : []
|
|
194
|
+
end
|
|
195
|
+
|
|
215
196
|
def descend(node, scope, name)
|
|
216
197
|
node.child_nodes.compact.each do |child|
|
|
217
198
|
found = body_of(child, scope, name)
|
|
@@ -236,6 +217,26 @@ module RailsAiContext
|
|
|
236
217
|
call && literal(call.arguments&.arguments)
|
|
237
218
|
end
|
|
238
219
|
|
|
220
|
+
# A name, or a composite key's names; nil for anything computed.
|
|
221
|
+
def primary_key_assigned(body)
|
|
222
|
+
call = body.find { |node| node.is_a?(Prism::CallNode) && node.name == :primary_key= && node.receiver.is_a?(Prism::SelfNode) }
|
|
223
|
+
args = call&.arguments&.arguments
|
|
224
|
+
return literal(args) unless args&.size == 1 && args.first.is_a?(Prism::ArrayNode)
|
|
225
|
+
|
|
226
|
+
names = args.first.elements.map { |element| literal([ element ]) }
|
|
227
|
+
names if names.all?
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def boolean_assigned(body, name)
|
|
231
|
+
call = body.find do |node|
|
|
232
|
+
node.is_a?(Prism::CallNode) && node.name == name && node.receiver.is_a?(Prism::SelfNode)
|
|
233
|
+
end
|
|
234
|
+
args = call&.arguments&.arguments
|
|
235
|
+
return nil unless args&.size == 1
|
|
236
|
+
|
|
237
|
+
{ Prism::TrueNode => true, Prism::FalseNode => false }[args.first.class]
|
|
238
|
+
end
|
|
239
|
+
|
|
239
240
|
# `self.table_name = "#{table_name_prefix}users#{table_name_suffix}"`: the class's
|
|
240
241
|
# own affix, else the app's configured one.
|
|
241
242
|
def interpolated(body, own, root)
|
|
@@ -253,7 +254,7 @@ module RailsAiContext
|
|
|
253
254
|
next part.unescaped if part.is_a?(Prism::StringNode)
|
|
254
255
|
|
|
255
256
|
key = affix_read(part) or return nil
|
|
256
|
-
own[key] ||
|
|
257
|
+
own[key] || ActiveRecordSettings.for(root)[key] || ""
|
|
257
258
|
end.join
|
|
258
259
|
end
|
|
259
260
|
|
|
@@ -284,8 +285,8 @@ module RailsAiContext
|
|
|
284
285
|
end
|
|
285
286
|
end
|
|
286
287
|
|
|
287
|
-
private_class_method :affix, :read, :body_of, :descend, :statements, :segment,
|
|
288
|
-
:assigned, :returned, :literal, :read_namespace_prefixes,
|
|
288
|
+
private_class_method :affix, :read, :body_of, :built_body, :descend, :statements, :segment,
|
|
289
|
+
:assigned, :boolean_assigned, :primary_key_assigned, :returned, :literal, :read_namespace_prefixes,
|
|
289
290
|
:interpolated, :affixed_node, :affix_read,
|
|
290
291
|
:engine_files, :collect_isolate_calls
|
|
291
292
|
end
|