Module: Glancer::Indexer::SchemaIndexer

Defined in:
lib/glancer/indexer/schema_indexer.rb

Class Method Summary collapse

Class Method Details

.eager_load_models!Object



60
61
62
63
64
65
# File 'lib/glancer/indexer/schema_indexer.rb', line 60

def eager_load_models!
  Rails.application.eager_load!
  Glancer::Utils::Logger.debug("Indexer::SchemaIndexer", "Models eager-loaded for association reflection")
rescue StandardError => e
  Glancer::Utils::Logger.warn("Indexer::SchemaIndexer", "Could not eager-load models: #{e.message}")
end

.extract_foreign_keys(schema_text, schema_file) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/glancer/indexer/schema_indexer.rb', line 142

def extract_foreign_keys(schema_text, schema_file)
  lines = schema_text.lines.select { |l| l.strip.start_with?("add_foreign_key") }
  return nil if lines.empty?

  relationships = lines.filter_map do |line|
    # add_foreign_key "orders", "users", column: "user_id"
    # add_foreign_key "order_items", "orders"
    m = line.match(/add_foreign_key ["'](\w+)["'],\s*["'](\w+)["'](?:.*column:\s*["'](\w+)["'])?/)
    next unless m

    child_table = m[1]
    parent_table = m[2]
    column = m[3] || "#{parent_table.singularize}_id"
    "#{child_table}.#{column}#{parent_table}.id"
  end

  return nil if relationships.empty?

  content = "# Foreign Key Relationships\n#{relationships.join("\n")}"
  Glancer::Utils::Logger.debug("Indexer::SchemaIndexer", "Extracted #{relationships.size} foreign key(s)")

  {
    content: content,
    source_type: "schema",
    source_path: "#{schema_file}#foreign_keys"
  }
end

.extract_inflectionsObject



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/glancer/indexer/schema_indexer.rb', line 112

def extract_inflections
  inflections_file = Rails.root.join("config/initializers/inflections.rb")
  return nil unless File.exist?(inflections_file)

  raw = File.read(inflections_file)
  return nil unless raw.lines.any? { |l| l.strip.match?(/\binflect\.\w/) }

  Glancer::Utils::Logger.debug("Indexer::SchemaIndexer", "Found custom inflections, adding as schema chunk")
  {
    content: "# Custom Rails Inflections\n# These control plural/singular model name mapping.\n\n#{raw.strip}",
    source_type: "schema",
    source_path: inflections_file.to_s
  }
rescue StandardError => e
  Glancer::Utils::Logger.warn("Indexer::SchemaIndexer", "Could not read inflections: #{e.message}")
  nil
end

.extract_table_name(chunk) ⇒ Object



138
139
140
# File 'lib/glancer/indexer/schema_indexer.rb', line 138

def extract_table_name(chunk)
  chunk[/create_table ["']?([a-zA-Z0-9_]+)["']?/, 1]
end

.find_model_for_table(table_name) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/glancer/indexer/schema_indexer.rb', line 67

def find_model_for_table(table_name)
  candidates = ActiveRecord::Base.descendants.select do |model|
    !model.abstract_class? &&
      !model.name&.start_with?("Glancer::") &&
      model.table_name == table_name
  end
  return nil if candidates.empty?

  candidates.find { |m| m.superclass.abstract_class? || m.superclass == ActiveRecord::Base } || candidates.first
rescue StandardError
  nil
end

.format_association(assoc) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/glancer/indexer/schema_indexer.rb', line 97

def format_association(assoc)
  parts = ["  #{assoc.macro} :#{assoc.name}"]
  opts = ["class_name: \"#{assoc.class_name}\""]

  fk = assoc.foreign_key.to_s
  opts << "foreign_key: \"#{fk}\"" if fk.present?
  opts << "through: :#{assoc.options[:through]}" if assoc.options[:through].present?
  opts << "polymorphic: true" if assoc.options[:polymorphic]
  opts << "as: :#{assoc.options[:as]}" if assoc.options[:as].present?
  opts << "source: :#{assoc.options[:source]}" if assoc.options[:source].present?
  opts << "dependent: :#{assoc.options[:dependent]}" if assoc.options[:dependent].present?

  "#{parts.join} (#{opts.join(", ")})"
end

.index!Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/glancer/indexer/schema_indexer.rb', line 8

def index!
  Glancer::Utils::Logger.info("Indexer::SchemaIndexer", "Starting schema indexing...")

  schema_file = Rails.root.join("db/schema.rb")

  unless File.exist?(schema_file)
    Glancer::Utils::Logger.warn("Indexer::SchemaIndexer", "Schema file not found at: #{schema_file}")
    return []
  end

  Glancer::Utils::Logger.debug("Indexer::SchemaIndexer", "Reading schema file from: #{schema_file}")

  content = File.read(schema_file)
  Glancer::Utils::Logger.debug("Indexer::SchemaIndexer", "Read #{content.bytesize} bytes from schema file")

  eager_load_models!

  chunks = split_into_chunks(content)
  Glancer::Utils::Logger.info("Indexer::SchemaIndexer", "Found #{chunks.size} table definition(s) in schema")

  indexed_chunks = chunks.map do |chunk|
    table_name = extract_table_name(chunk)
    if table_name
      Glancer::Utils::Logger.debug("Indexer::SchemaIndexer", "Indexed table: #{table_name}")
      enriched = chunk + model_associations_block(table_name)
      {
        content: enriched,
        source_type: "schema",
        source_path: "#{schema_file}##{table_name}"
      }
    else
      Glancer::Utils::Logger.warn("Indexer::SchemaIndexer", "Could not extract table name from chunk")
      nil
    end
  end.compact

  fk_chunk = extract_foreign_keys(content, schema_file)
  indexed_chunks << fk_chunk if fk_chunk

  inflections_chunk = extract_inflections
  indexed_chunks << inflections_chunk if inflections_chunk

  Glancer::Utils::Logger.info("Indexer::SchemaIndexer",
                              "Completed schema indexing. Total indexed chunks: #{indexed_chunks.size}")

  indexed_chunks
rescue StandardError => e
  Glancer::Utils::Logger.error("Indexer::SchemaIndexer", "Schema indexing failed: #{e.class} - #{e.message}")
  Glancer::Utils::Logger.debug("Indexer::SchemaIndexer", "Backtrace:\n#{e.backtrace.join("\n")}")
  raise Glancer::Error, "Schema indexing failed: #{e.message}"
end

.model_associations_block(table_name) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/glancer/indexer/schema_indexer.rb', line 80

def model_associations_block(table_name)
  model = find_model_for_table(table_name)
  return "" unless model

  assocs = model.reflect_on_all_associations
  return "" if assocs.empty?

  lines = assocs.filter_map do |assoc|
    format_association(assoc)
  rescue StandardError
    nil
  end
  return "" if lines.empty?

  "\n\n# ActiveRecord Associations (#{model.name}):\n#{lines.join("\n")}"
end

.split_into_chunks(schema_text) ⇒ Object



130
131
132
133
134
135
136
# File 'lib/glancer/indexer/schema_indexer.rb', line 130

def split_into_chunks(schema_text)
  schema_text.split(/^  create_table /).map do |chunk|
    next if chunk.strip.empty?

    "create_table #{chunk.strip}"
  end.compact
end