Class: Mbeditor::ModelGraphService
- Inherits:
-
Object
- Object
- Mbeditor::ModelGraphService
- Defined in:
- app/services/mbeditor/model_graph_service.rb
Overview
Builds a graph of the host app's ActiveRecord models and the associations between them, for the Models sidebar tab.
Reads the associations by reflection rather than by parsing model files.
mbeditor already runs inside the host app, so reflect_on_all_associations
is right there — and it resolves class_name:, through:, polymorphic and
inverse sides correctly, which regex or AST parsing of has_many lines
silently gets wrong. (ruby-lsp cannot help here: its index models constants
and methods, not associations.)
Deliberately never touches the database connection. Reflections are pure metadata, so this works on an app whose database isn't running or migrated; column counts are only filled in when a connection happens to be live.
Constant Summary collapse
- IGNORED_NAMESPACES =
Rails' own models — ActiveStorage, ActionText, SolidQueue, schema migrations — are not what you opened this tab to look at.
%w[ ActiveRecord ActiveStorage ActionText ActionMailbox SolidQueue SolidCache SolidCable ].freeze
- DEFAULT_MAX_MODELS =
Cap on models drawn. 300 was chosen before the layout could handle that many; a schema slightly over it silently lost models and only said "(truncated)". Raised, and configurable for anything larger — the cost of a bigger graph is now the browser's rendering, not the layout.
1000- MAX_COLUMNS_SENT =
Only the first few columns travel: the diagram box shows that many and the full list is a click away in the schema modal, which fetches its own data from /model_schema.
8
Class Method Summary collapse
- .call(workspace_root) ⇒ Object
-
.fingerprint_for(root) ⇒ Object
A cheap stand-in for "have the models changed": the newest mtime and the file count across the model and migration directories.
-
.invalidate(_root = nil) ⇒ Object
Only needed for an explicit refresh: ordinary saves are picked up by the fingerprint below, so there is no file-change hook to keep in sync.
- .max_models ⇒ Object
Class Method Details
.call(workspace_root) ⇒ Object
42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'app/services/mbeditor/model_graph_service.rb', line 42 def call(workspace_root) root = workspace_root.to_s fingerprint = fingerprint_for(root) cached = MUTEX.synchronize { @cache } return cached[:payload] if cached && cached[:fingerprint] == fingerprint && cached[:root] == root payload = build(root) payload[:fingerprint] = fingerprint MUTEX.synchronize { @cache = { root: root, fingerprint: fingerprint, payload: payload } } write_artifacts(root, payload) payload end |
.fingerprint_for(root) ⇒ Object
A cheap stand-in for "have the models changed": the newest mtime and the file count across the model and migration directories. Avoids eager-loading the app just to decide whether the cache is stale.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'app/services/mbeditor/model_graph_service.rb', line 66 def fingerprint_for(root) dirs = [File.join(root, "app", "models"), File.join(root, "db", "migrate")] newest = 0 count = 0 dirs.each do |dir| next unless File.directory?(dir) Dir.glob(File.join(dir, "**", "*.rb")).each do |f| count += 1 mtime = File.mtime(f).to_i newest = mtime if mtime > newest end end "#{newest}:#{count}" rescue StandardError "unknown" end |
.invalidate(_root = nil) ⇒ Object
Only needed for an explicit refresh: ordinary saves are picked up by the fingerprint below, so there is no file-change hook to keep in sync.
58 59 60 61 |
# File 'app/services/mbeditor/model_graph_service.rb', line 58 def invalidate(_root = nil) MUTEX.synchronize { @cache = nil } nil end |
.max_models ⇒ Object
31 32 33 34 |
# File 'app/services/mbeditor/model_graph_service.rb', line 31 def self.max_models value = Mbeditor.configuration.model_graph_max_models.to_i value.positive? ? value : DEFAULT_MAX_MODELS end |