Class: Mbeditor::RubyDefinitionService

Inherits:
Object
  • Object
show all
Defined in:
app/services/mbeditor/ruby_definition_service.rb

Overview

Searches .rb files in a workspace for definitions of a named Ruby method using Ripper’s AST parser (no subprocesses).

Returns an array of hashes, each describing one definition site:

{
  file:      String,  # workspace-relative path
  line:      Integer, # 1-based line number of the `def` keyword
  signature: String,  # trimmed source text of the def line
  comments:  String   # leading # comment lines immediately above the def (may be empty)
}

Usage:

results = RubyDefinitionService.call(workspace_root, "my_method",
                                     excluded_dirnames: %w[tmp .git])

Constant Summary collapse

MAX_RESULTS =
20
MAX_COMMENT_LOOKAHEAD =
15

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(workspace_root, symbol, excluded_dirnames: [], excluded_paths: []) ⇒ RubyDefinitionService

Returns a new instance of RubyDefinitionService.



94
95
96
97
98
99
# File 'app/services/mbeditor/ruby_definition_service.rb', line 94

def initialize(workspace_root, symbol, excluded_dirnames: [], excluded_paths: [])
  @workspace_root   = workspace_root.to_s.chomp("/")
  @symbol           = symbol
  @excluded_dirnames = Array(excluded_dirnames)
  @excluded_paths    = Array(excluded_paths)
end

Class Attribute Details

.cache_pathObject

Returns the value of attribute cache_path.



38
39
40
# File 'app/services/mbeditor/ruby_definition_service.rb', line 38

def cache_path
  @cache_path
end

.file_cacheObject (readonly)

Returns the value of attribute file_cache.



37
38
39
# File 'app/services/mbeditor/ruby_definition_service.rb', line 37

def file_cache
  @file_cache
end

.mutexObject (readonly)

Returns the value of attribute mutex.



37
38
39
# File 'app/services/mbeditor/ruby_definition_service.rb', line 37

def mutex
  @mutex
end

Class Method Details

.call(workspace_root, symbol, excluded_dirnames: [], excluded_paths: []) ⇒ Object



40
41
42
43
44
# File 'app/services/mbeditor/ruby_definition_service.rb', line 40

def call(workspace_root, symbol, excluded_dirnames: [], excluded_paths: [])
  new(workspace_root, symbol,
      excluded_dirnames: excluded_dirnames,
      excluded_paths: excluded_paths).call
end

.clear_cache!Object

Exposed for tests.



85
86
87
88
89
90
91
# File 'app/services/mbeditor/ruby_definition_service.rb', line 85

def clear_cache!
  @mutex.synchronize { @file_cache.clear; @cache_loaded = false }
  path = @cache_path.to_s
  File.delete(path) if !path.empty? && File.exist?(path)
rescue StandardError
  nil
end

.load_disk_cache_onceObject

Load the JSON cache from disk exactly once per process (double-checked under the mutex so concurrent first-calls don’t double-load).



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'app/services/mbeditor/ruby_definition_service.rb', line 48

def load_disk_cache_once
  return if @cache_loaded

  @mutex.synchronize do
    return if @cache_loaded

    @cache_loaded = true
    path = @cache_path.to_s
    return if path.empty? || !File.exist?(path)

    raw = JSON.parse(File.read(path))
    raw.each do |abs_path, entry|
      @file_cache[abs_path] = {
        mtime:    entry["mtime"].to_f,
        lines:    entry["lines"],
        all_defs: entry["all_defs"]
      }
    end
  rescue StandardError
    nil # corrupted or incompatible cache file — start fresh
  end
end

.persist_cacheObject

Atomically write the in-memory cache to disk (tmp-file + rename).



72
73
74
75
76
77
78
79
80
81
82
# File 'app/services/mbeditor/ruby_definition_service.rb', line 72

def persist_cache
  path = @cache_path.to_s
  return if path.empty?

  snapshot = @mutex.synchronize { @file_cache.dup }
  tmp_path = "#{path}.tmp"
  File.write(tmp_path, JSON.generate(snapshot))
  File.rename(tmp_path, path)
rescue StandardError
  nil
end

Instance Method Details

#callObject



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'app/services/mbeditor/ruby_definition_service.rb', line 101

def call
  self.class.load_disk_cache_once

  results     = []
  @new_entries = false

  Find.find(@workspace_root) do |path|
    # Prune excluded directories
    if File.directory?(path)
      dirname = File.basename(path)
      rel_dir = relative_path(path)
      if path != @workspace_root && excluded_dir?(dirname, rel_dir)
        Find.prune
      end
      next
    end

    next unless path.end_with?(".rb")

    rel = relative_path(path)
    next if excluded_rel_path?(rel, File.basename(path))

    begin
      cached = cache_entry_for(path)
      next unless cached

      hit_lines = cached[:all_defs][@symbol]
      next unless hit_lines && hit_lines.any?

      hit_lines.each do |def_line|
        results << {
          file:      rel,
          line:      def_line,
          signature: (cached[:lines][def_line - 1] || "").strip,
          comments:  extract_comments(cached[:lines], def_line)
        }
        return results if results.length >= MAX_RESULTS
      end
    rescue StandardError
      # Malformed file or unreadable; skip silently
    end
  end

  self.class.persist_cache if @new_entries
  results
end