Class: RailsAiBridge::RubydexAdapter

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_ai_bridge/rubydex_adapter.rb,
lib/rails_ai_bridge/rubydex_adapter/indexer.rb,
lib/rails_ai_bridge/rubydex_adapter/serializer.rb,
lib/rails_ai_bridge/rubydex_adapter/method_counter.rb

Overview

Wrapper class for Shopify's rubydex semantic analysis API.

Provides a high-level interface for indexing Ruby source files and querying semantic information such as declarations, references, ancestors, and codebase statistics.

Rubydex is an optional dependency. When not installed, all query methods return empty results and RubydexAdapter.available? returns +false+.

Defined Under Namespace

Classes: Indexer, MethodCounter, Serializer

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root) ⇒ RubydexAdapter

Returns a new instance of RubydexAdapter.

Parameters:

  • root (String)

    the project root directory to index



68
69
70
71
72
73
74
75
# File 'lib/rails_ai_bridge/rubydex_adapter.rb', line 68

def initialize(root)
  @root = root
  @graph = nil
  @indexed = false
  @serializer = Serializer.new(@root)
  @indexer = Indexer.new
  @method_counter = MethodCounter.new(serializer: @serializer)
end

Instance Attribute Details

#graphRubydex::Graph? (readonly)

Returns the underlying rubydex graph instance.

Returns:

  • (Rubydex::Graph, nil)

    the underlying rubydex graph instance



14
15
16
# File 'lib/rails_ai_bridge/rubydex_adapter.rb', line 14

def graph
  @graph
end

Class Method Details

.available?Boolean

Whether the rubydex gem is installed and loadable.

Returns:

  • (Boolean)

Raises:

  • (LoadError)

    rescued internally, returns false



21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/rails_ai_bridge/rubydex_adapter.rb', line 21

def available?
  if @available.nil?
    @available = begin
      require 'rubydex'
      true
    rescue LoadError
      false
    end
  end

  @available
end

.instance(root = nil) ⇒ RubydexAdapter

Returns a shared adapter instance, building the index if needed. Thread-safe via +Mutex+.

Parameters:

  • root (String) (defaults to: nil)

    the Rails root path to index

Returns:

Raises:

  • (StandardError)

    on indexing failure (graph set to nil, indexed to false)



47
48
49
50
51
52
53
54
55
56
57
# File 'lib/rails_ai_bridge/rubydex_adapter.rb', line 47

def instance(root = nil)
  @mutex ||= Mutex.new
  @mutex.synchronize do
    root ||= Rails.root.to_s
    @instance = nil if @instance && @instance_root != root
    @instance ||= begin
      @instance_root = root
      new(root).tap(&:index!)
    end
  end
end

.reset!void

This method returns an undefined value.

Clears the shared instance (useful in tests or when reindexing).



62
63
64
# File 'lib/rails_ai_bridge/rubydex_adapter.rb', line 62

def reset!
  @mutex&.synchronize { @instance = nil }
end

.reset_availability!void

This method returns an undefined value.

Resets the cached availability check (useful in tests).



37
38
39
# File 'lib/rails_ai_bridge/rubydex_adapter.rb', line 37

def reset_availability!
  @available = nil
end

Instance Method Details

#all_declarationsArray<Hash>

Get all declarations in the graph.

Returns:

  • (Array<Hash>)

    array of declaration summaries

Raises:

  • (StandardError)

    rescued internally, returns empty array



144
145
146
147
148
149
150
# File 'lib/rails_ai_bridge/rubydex_adapter.rb', line 144

def all_declarations
  return [] unless indexed?

  @graph.declarations.map { |decl| @serializer.declaration_to_hash(decl) }
rescue StandardError
  []
end

#ancestors(name) ⇒ Array<String>

Get ancestors (superclasses/included modules) of a declaration.

Parameters:

  • name (String)

    fully qualified name

Returns:

  • (Array<String>)

    ancestor names

Raises:

  • (StandardError)

    rescued internally, returns empty array



189
190
191
192
193
194
195
196
197
198
# File 'lib/rails_ai_bridge/rubydex_adapter.rb', line 189

def ancestors(name)
  return [] unless indexed?

  decl = @graph[name]
  return [] unless decl.respond_to?(:ancestors)

  decl.ancestors.map(&:name)
rescue StandardError
  []
end

#codebase_statsHash

High-level codebase statistics from the rubydex index.

Returns:

  • (Hash)

    statistics about the indexed codebase

Raises:

  • (StandardError)

    rescued internally, returns empty hash



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/rails_ai_bridge/rubydex_adapter.rb', line 219

def codebase_stats
  return {} unless indexed?

  declarations = @graph.declarations.to_a
  documents = @graph.documents.to_a

  classes = declarations.select { |d| class_declaration?(d) }
  modules = declarations.select { |d| module_declaration?(d) }

  {
    total_files: documents.size,
    total_declarations: declarations.size,
    total_classes: classes.size,
    total_modules: modules.size,
    total_methods: @method_counter.count(declarations),
    total_constant_references: safe_count(@graph, :constant_references),
    total_method_references: safe_count(@graph, :method_references)
  }
rescue StandardError
  {}
end

#constant_referencesArray<Hash>

Get all constant references across the codebase.

Returns:

  • (Array<Hash>)

    constant reference details

Raises:

  • (StandardError)

    rescued internally, returns empty array



204
205
206
207
208
209
210
211
212
213
# File 'lib/rails_ai_bridge/rubydex_adapter.rb', line 204

def constant_references
  return [] unless indexed?

  @graph.constant_references.map do |ref|
    { name: ref.respond_to?(:name) ? ref.name : ref.to_s,
      location: ref.respond_to?(:location) ? @serializer.format_location(ref.location) : nil }.compact
  end
rescue StandardError
  []
end

#descendants(name) ⇒ Array<String>

Get descendants (subclasses/includers) of a declaration.

Parameters:

  • name (String)

    fully qualified name

Returns:

  • (Array<String>)

    descendant names

Raises:

  • (StandardError)

    rescued internally, returns empty array



173
174
175
176
177
178
179
180
181
182
# File 'lib/rails_ai_bridge/rubydex_adapter.rb', line 173

def descendants(name)
  return [] unless indexed?

  decl = @graph[name]
  return [] unless decl.respond_to?(:descendants)

  decl.descendants.map(&:name)
rescue StandardError
  []
end

#file_declarations(path) ⇒ Array<Hash>

Get declarations defined in a specific file.

Parameters:

  • path (String)

    relative or absolute file path

Returns:

  • (Array<Hash>)

    declarations found in the file

Raises:

  • (StandardError)

    rescued internally, returns empty array



157
158
159
160
161
162
163
164
165
166
# File 'lib/rails_ai_bridge/rubydex_adapter.rb', line 157

def file_declarations(path)
  return [] unless indexed?

  doc = @graph.documents.find { |d| d.uri.end_with?(path) || d.uri == path }
  return [] unless doc

  doc.definitions.map { |defn| @serializer.definition_to_hash(defn) }
rescue StandardError
  []
end

#get_declaration(name) ⇒ Hash?

Get a declaration by its fully qualified name.

Parameters:

  • name (String)

    fully qualified name (e.g. "Foo::Bar")

Returns:

  • (Hash, nil)

    declaration details or nil if not found

Raises:

  • (StandardError)

    rescued internally, returns nil



129
130
131
132
133
134
135
136
137
138
# File 'lib/rails_ai_bridge/rubydex_adapter.rb', line 129

def get_declaration(name)
  return nil unless indexed?

  decl = @graph[name]
  return nil unless decl

  @serializer.detailed_declaration_to_hash(decl)
rescue StandardError
  nil
end

#index!void

This method returns an undefined value.

Builds the rubydex graph index for the project. No-op if rubydex is not available or already indexed.

Raises:

  • (StandardError)

    rescued internally, sets indexed to false



82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/rails_ai_bridge/rubydex_adapter.rb', line 82

def index!
  return if @indexed || !self.class.available?

  @graph = @indexer.build(@root)
  @indexed = true
rescue StandardError => error
  Rails.logger.warn('rubydex.indexing_failed', {
                      event: 'rubydex.indexing_failed',
                      error: error.message,
                      backtrace: error.backtrace.first(5).join("\n")
                    })
  @graph = nil
  @indexed = false
end

#indexed?Boolean

Whether the graph has been successfully indexed.

Returns:

  • (Boolean)


100
101
102
# File 'lib/rails_ai_bridge/rubydex_adapter.rb', line 100

def indexed?
  @indexed && @graph.present?
end

#search(query, max_results: 20) ⇒ Array<Hash>

Search declarations by name using rubydex fuzzy search.

Parameters:

  • query (String)

    search query (e.g. "User", "Foo#bar")

  • max_results (Integer) (defaults to: 20)

    maximum number of results to return

Returns:

  • (Array<Hash>)

    array of declaration summaries

Raises:

  • (StandardError)

    rescued internally, returns empty array



110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/rails_ai_bridge/rubydex_adapter.rb', line 110

def search(query, max_results: 20)
  return [] unless indexed?

  results = @graph.search(query)
  results.first(max_results).map { |decl| @serializer.declaration_to_hash(decl) }
rescue StandardError => error
  Rails.logger.warn('rubydex.search_failed', {
                      event: 'rubydex.search_failed',
                      error: error.message,
                      backtrace: error.backtrace.first(5).join("\n")
                    })
  []
end