Class: Dbwatcher::Services::DiagramAnalyzers::ForeignKeyAnalyzer

Inherits:
BaseAnalyzer show all
Defined in:
lib/dbwatcher/services/diagram_analyzers/foreign_key_analyzer.rb

Overview

Analyzes relationships based on database schema foreign keys

This service examines the actual database schema to detect foreign key relationships between tables that were involved in a session.

Examples:

analyzer = ForeignKeyAnalyzer.new(session)
dataset = analyzer.call

Instance Method Summary collapse

Methods inherited from BaseAnalyzer

#call

Methods inherited from BaseService

call, #call

Methods included from Logging

#debug_enabled?, #log_debug, #log_error, #log_info, #log_warn

Constructor Details

#initialize(session = nil) ⇒ ForeignKeyAnalyzer

Initialize with session

Parameters:

  • session (Session) (defaults to: nil)

    session to analyze (optional for global analysis)



18
19
20
21
22
23
# File 'lib/dbwatcher/services/diagram_analyzers/foreign_key_analyzer.rb', line 18

def initialize(session = nil)
  @session = session
  @connection = ActiveRecord::Base.connection if defined?(ActiveRecord::Base)
  @session_tables = session ? extract_session_tables : []
  super()
end

Instance Method Details

#analyze(_context) ⇒ Array<Hash>

Analyze schema relationships

Parameters:

  • context (Hash)

    analysis context

Returns:

  • (Array<Hash>)

    array of relationship data



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/dbwatcher/services/diagram_analyzers/foreign_key_analyzer.rb', line 29

def analyze(_context)
  return [] unless schema_available?

  Rails.logger.debug "ForeignKeyAnalyzer: Starting analysis with #{tables_to_analyze.length} tables"
  relationships = extract_foreign_key_relationships

  # Log some sample data to help with debugging
  if relationships.any?
    sample_relationship = relationships.first
    Rails.logger.debug "ForeignKeyAnalyzer: Sample relationship - " \
                       "from_table: #{sample_relationship[:from_table]}, " \
                       "to_table: #{sample_relationship[:to_table]}, " \
                       "type: #{sample_relationship[:type]}"
  else
    Rails.logger.info "ForeignKeyAnalyzer: No relationships found"
  end

  relationships
end

#analyzer_typeString

Get analyzer type

Returns:

  • (String)

    analyzer type identifier



120
121
122
# File 'lib/dbwatcher/services/diagram_analyzers/foreign_key_analyzer.rb', line 120

def analyzer_type
  "foreign_key"
end

#transform_to_dataset(raw_data) ⇒ DiagramData::Dataset

Transform raw relationship data to Dataset

Parameters:

  • raw_data (Array<Hash>)

    raw relationship data

Returns:



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/dbwatcher/services/diagram_analyzers/foreign_key_analyzer.rb', line 53

def transform_to_dataset(raw_data)
  dataset = create_empty_dataset
  dataset..merge!({
                            total_relationships: raw_data.length,
                            tables_analyzed: tables_to_analyze.length
                          })

  # Create entities for each unique table
  table_entities = {}

  # First, collect all unique tables from the relationships
  tables = []
  raw_data.each do |relationship|
    tables << relationship[:from_table] if relationship[:from_table]
    tables << relationship[:to_table] if relationship[:to_table]
  end
  tables.uniq!

  # Create entities for all tables
  tables.each do |table_name|
    entity = create_entity_with_columns(table_name)
    dataset.add_entity(entity)
    table_entities[table_name] = entity
  end

  # Create relationships in a separate loop
  raw_data.each do |relationship|
    next unless relationship[:from_table] && relationship[:to_table]

    # Include self-referential relationships (source and target are the same)
    # but log them for debugging
    if relationship[:from_table] == relationship[:to_table]
      Rails.logger.info "ForeignKeyAnalyzer: Including self-referential relationship for " \
                        "#{relationship[:from_table]} " \
                        "(#{relationship[:from_column]} -> #{relationship[:to_column]})"
    end

    cardinality = determine_cardinality(relationship)

    relationship_obj = create_relationship({
                                             source_id: relationship[:from_table],
                                             target_id: relationship[:to_table],
                                             type: relationship[:type],
                                             label: relationship[:constraint_name] ||
            relationship[:from_column],
                                             cardinality: cardinality,
                                             metadata: {
                                               constraint_name: relationship[:constraint_name],
                                               from_column: relationship[:from_column],
                                               to_column: relationship[:to_column],
                                               on_delete: relationship[:on_delete],
                                               on_update: relationship[:on_update],
                                               original_type: relationship[:type],
                                               self_referential: relationship[:from_table] ==
                          relationship[:to_table]
                                             }
                                           })

    dataset.add_relationship(relationship_obj)
  end

  dataset
end