dbwatcher 1.1.3 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc4ee903407a594933016fc81eab8e7ea90b7f63fe6b046305191b18e87afc27
4
- data.tar.gz: e1e9c3f66d04da6e5e043feddf01fb19e02680f0556799e06c5a74ef44da0143
3
+ metadata.gz: 4b14e37821dd10efdc21bfa6e9a68ff8ffea9096d616616893944aca66dd1c58
4
+ data.tar.gz: 983a92a4065533bdb7f15a0adeeefc3fd0a97109cf5b08a9c4adaef11bd829f9
5
5
  SHA512:
6
- metadata.gz: 66c026987650f546ebe5ca2f0ef513d0828f3ca615d28dfeb55174c36c33db4dd1361cea07812905f43d70eab4a369f9ddf0c483191b3337b22f89c699412b19
7
- data.tar.gz: 95d9a01e5dd54e81d9a1f6260720fb43d2ef650a3c550012cb943d39c66e4d13616d796a39b489bdffdc9816f30d6bedbd78ad3c0537c588ba120821964c3e19
6
+ metadata.gz: 86071979a9e26aa6e0e5f289b4079804615e4477357dc894305d8eab047c328c9a1d97651e37889c913f073fc8f156abd410582adbe8581f68443f3d195fc5f4
7
+ data.tar.gz: '09bacda0cc050854b53d8567b46c791cfc440825598bb3c06300e2f4f572dd1f3cde1df0b127a332edd9a3995077ef7beae9bafc47cb7629478d2671a0d2691a'
@@ -8,32 +8,23 @@ module Dbwatcher
8
8
 
9
9
  def tables_data
10
10
  Rails.logger.info "API::V1::SessionsController#tables_data: Getting tables for session #{@session.id}"
11
-
12
- # Paginated, filtered tables data
13
- # Convert ActionController::Parameters to a hash before passing to service
14
- service = Dbwatcher::Services::Api::TablesDataService.new(@session, filter_params.to_h)
11
+ service = Dbwatcher::Services::Api::TablesDataService.new(@session, tables_data_params)
15
12
  render json: service.call
16
13
  end
17
14
 
18
15
  def summary_data
19
16
  Rails.logger.info "API::V1::SessionsController#summary_data: Getting summary for session #{@session.id}"
20
-
21
- # Aggregated summary statistics
22
17
  service = Dbwatcher::Services::Api::SummaryDataService.new(@session)
23
18
  render json: service.call
24
19
  end
25
20
 
26
21
  def diagram_data
27
22
  Rails.logger.info "API::V1::SessionsController#diagram_data: Getting diagram for session #{@session.id}"
28
-
29
- # Generated diagram content with caching
30
- # Convert ActionController::Parameters to a hash before passing to service
31
- diagram_params = params.to_unsafe_h
32
23
  service = Dbwatcher::Services::Api::DiagramDataService.new(@session, params[:type], diagram_params)
33
24
  result = service.call
34
25
 
35
26
  if result[:error]
36
- render json: { error: result[:error] }, status: :unprocessable_entity
27
+ render_error(result[:error])
37
28
  else
38
29
  render json: result
39
30
  end
@@ -41,13 +32,11 @@ module Dbwatcher
41
32
 
42
33
  def timeline_data
43
34
  Rails.logger.info "API::V1::SessionsController#timeline_data: Getting timeline for session #{@session.id}"
44
-
45
- # Timeline data processed from session changes
46
35
  service = Dbwatcher::Services::TimelineDataService.new(@session)
47
36
  result = service.call
48
37
 
49
38
  if result[:errors].any?
50
- render json: { error: result[:errors].first[:message] }, status: :unprocessable_entity
39
+ render_error(result[:errors].first[:message])
51
40
  else
52
41
  render json: result
53
42
  end
@@ -55,7 +44,6 @@ module Dbwatcher
55
44
 
56
45
  def diagram_types
57
46
  Rails.logger.info "API::V1::SessionsController#diagram_types: Getting available diagram types"
58
-
59
47
  render json: {
60
48
  types: Dbwatcher::Services::Api::DiagramDataService.available_types_with_metadata,
61
49
  default_type: "database_tables"
@@ -66,11 +54,19 @@ module Dbwatcher
66
54
 
67
55
  def find_session
68
56
  @session = Storage.sessions.find(params[:id])
69
- render json: { error: "Session not found" }, status: :not_found unless @session
57
+ render_error("Session not found", :not_found) unless @session
58
+ end
59
+
60
+ def tables_data_params
61
+ params.permit(:id, :table, :operation, :page, :per_page, session: {}).to_h
62
+ end
63
+
64
+ def diagram_params
65
+ params.permit(:type, :format, :include_columns, :show_relationships, session: {}).to_h
70
66
  end
71
67
 
72
- def filter_params
73
- params.permit(:table, :operation, :page, :per_page)
68
+ def render_error(message, status = :unprocessable_entity)
69
+ render json: { error: message }, status: status
74
70
  end
75
71
  end
76
72
  end
@@ -26,6 +26,9 @@ module Dbwatcher
26
26
  :collect_sensitive_env_vars, :system_info_cache_duration,
27
27
  :system_info_include_performance_metrics
28
28
 
29
+ # Logging configuration
30
+ attr_accessor :debug_logging
31
+
29
32
  # Initialize with default values
30
33
  def initialize
31
34
  # Storage configuration defaults
@@ -47,6 +50,9 @@ module Dbwatcher
47
50
 
48
51
  # Initialize system information configuration with defaults
49
52
  initialize_system_info_config
53
+
54
+ # Initialize logging configuration with defaults
55
+ initialize_logging_config
50
56
  end
51
57
 
52
58
  # Initialize diagram configuration with default values
@@ -73,6 +79,11 @@ module Dbwatcher
73
79
  @system_info_include_performance_metrics = true
74
80
  end
75
81
 
82
+ # Initialize logging configuration with default values
83
+ def initialize_logging_config
84
+ @debug_logging = false
85
+ end
86
+
76
87
  # Validate configuration
77
88
  #
78
89
  # @return [Boolean] true if configuration is valid
@@ -14,9 +14,12 @@ module Dbwatcher
14
14
  end
15
15
 
16
16
  # Log a debug message with optional context
17
+ # Only logs if debug mode is enabled
17
18
  # @param message [String] the log message
18
19
  # @param context [Hash] additional context data
19
20
  def log_debug(message, context = {})
21
+ return unless debug_enabled?
22
+
20
23
  log_with_level(:debug, message, context)
21
24
  end
22
25
 
@@ -34,6 +37,16 @@ module Dbwatcher
34
37
  log_with_level(:error, message, context)
35
38
  end
36
39
 
40
+ # Check if debug logging is enabled
41
+ # @return [Boolean] true if debug logging is enabled
42
+ def debug_enabled?
43
+ return Dbwatcher.configuration.debug_logging if defined?(Dbwatcher.configuration) &&
44
+ Dbwatcher.configuration.respond_to?(:debug_logging)
45
+ return Rails.env.development? if defined?(Rails)
46
+
47
+ false
48
+ end
49
+
37
50
  private
38
51
 
39
52
  def log_with_level(level, message, context)
@@ -51,7 +64,16 @@ module Dbwatcher
51
64
  end
52
65
 
53
66
  def component_name
54
- self.class.name.split("::").last
67
+ if is_a?(Module) && !is_a?(Class)
68
+ # For modules
69
+ name.to_s.split("::").last
70
+ elsif self.class.name
71
+ # For classes
72
+ self.class.name.split("::").last
73
+ else
74
+ # Fallback
75
+ "Logger"
76
+ end
55
77
  end
56
78
 
57
79
  def rails_logger
@@ -232,6 +232,37 @@ module Dbwatcher
232
232
  # @param primary_key [String, nil] optional primary key for testing
233
233
  # @return [Boolean] true if likely self-referential
234
234
  def self_referential_column?(column_name, table_name, primary_key = nil)
235
+ # Get the singular form of the table name
236
+ base_name = singularize(table_name)
237
+
238
+ # Special case for post_id in posts table - not a self-reference
239
+ return false if column_name == "#{base_name}_id" && table_name == "posts" && base_name == "post"
240
+
241
+ # Check primary key if this is a table-specific reference
242
+ if column_name == "#{base_name}_id"
243
+ if primary_key.nil?
244
+ begin
245
+ primary_key = connection.primary_key(table_name)
246
+ rescue StandardError
247
+ return false
248
+ end
249
+ end
250
+
251
+ return column_name != primary_key
252
+ end
253
+
254
+ # Use pattern matching to check various self-referential patterns
255
+ common_pattern?(column_name) ||
256
+ hierarchy_pattern?(column_name, base_name) ||
257
+ relationship_pattern?(column_name) ||
258
+ directional_pattern?(column_name)
259
+ end
260
+
261
+ # Check if column matches common self-referential patterns
262
+ #
263
+ # @param column_name [String] column name to check
264
+ # @return [Boolean] true if matches common patterns
265
+ def common_pattern?(column_name)
235
266
  # Common self-referential patterns
236
267
  self_ref_patterns = %w[
237
268
  parent_id
@@ -257,52 +288,47 @@ module Dbwatcher
257
288
  replied_to_id
258
289
  ]
259
290
 
260
- # Check for exact matches with common patterns
261
- return true if self_ref_patterns.include?(column_name)
262
-
263
- # Get the singular form of the table name
264
- base_name = singularize(table_name)
265
-
266
- # Special case for post_id in posts table - not a self-reference
267
- return false if column_name == "#{base_name}_id" && table_name == "posts" && base_name == "post"
268
-
269
- # Check for table-specific self-references (e.g., comment_id in comments table)
270
- if column_name == "#{base_name}_id"
271
- # Check if this is not the primary key column
272
- if primary_key.nil?
273
- begin
274
- primary_key = connection.primary_key(table_name)
275
- rescue StandardError
276
- return false
277
- end
278
- end
279
-
280
- return column_name != primary_key
281
- end
291
+ self_ref_patterns.include?(column_name)
292
+ end
282
293
 
283
- # Check for hierarchy patterns with table name
294
+ # Check for hierarchy patterns with table name
295
+ #
296
+ # @param column_name [String] column name to check
297
+ # @param base_name [String] singular form of table name
298
+ # @return [Boolean] true if matches hierarchy patterns
299
+ def hierarchy_pattern?(column_name, base_name)
284
300
  hierarchy_prefixes = %w[parent child ancestor descendant superior subordinate manager supervisor]
285
- hierarchy_prefixes.each do |prefix|
286
- # Check for patterns like parent_comment_id in comments table
287
- return true if column_name.start_with?("#{prefix}_#{base_name}_id")
288
301
 
289
- # Check for patterns like parent_of_id in any table
290
- return true if column_name.start_with?("#{prefix}_of_id")
302
+ hierarchy_prefixes.any? do |prefix|
303
+ # Check for patterns like parent_comment_id in comments table
304
+ column_name.start_with?("#{prefix}_#{base_name}_id") ||
305
+ # Check for patterns like parent_of_id in any table
306
+ column_name.start_with?("#{prefix}_of_id")
291
307
  end
308
+ end
292
309
 
293
- # Check for relationship patterns
310
+ # Check for relationship patterns
311
+ #
312
+ # @param column_name [String] column name to check
313
+ # @return [Boolean] true if matches relationship patterns
314
+ def relationship_pattern?(column_name)
294
315
  relationship_patterns = %w[related linked connected associated referenced]
295
- relationship_patterns.each do |pattern|
296
- return true if column_name.start_with?("#{pattern}_")
316
+
317
+ relationship_patterns.any? do |pattern|
318
+ column_name.start_with?("#{pattern}_")
297
319
  end
320
+ end
298
321
 
299
- # Check for directional patterns
322
+ # Check for directional patterns
323
+ #
324
+ # @param column_name [String] column name to check
325
+ # @return [Boolean] true if matches directional patterns
326
+ def directional_pattern?(column_name)
300
327
  directional_patterns = %w[previous next original copy source target]
301
- directional_patterns.each do |pattern|
302
- return true if column_name.start_with?("#{pattern}_")
303
- end
304
328
 
305
- false
329
+ directional_patterns.any? do |pattern|
330
+ column_name.start_with?("#{pattern}_")
331
+ end
306
332
  end
307
333
 
308
334
  # Analyze junction tables (many-to-many relationships)
@@ -5,81 +5,58 @@ require_relative "diagram_type_registry"
5
5
 
6
6
  module Dbwatcher
7
7
  module Services
8
- # Orchestrator for diagram generation using strategy pattern
8
+ # Service for generating diagrams from session data
9
9
  #
10
- # This service coordinates diagram generation by delegating to appropriate
11
- # analyzer and strategy classes with clean error handling.
10
+ # Coordinates the process of generating diagrams by:
11
+ # 1. Loading session data
12
+ # 2. Using appropriate analyzers to extract relationships
13
+ # 3. Applying diagram generation strategies
12
14
  #
13
15
  # @example
14
- # generator = DiagramGenerator.new(session_id, 'database_tables')
16
+ # generator = DiagramGenerator.new(session_id: "abc123", diagram_type: "database_tables")
15
17
  # result = generator.call
16
- # # => { content: "erDiagram\n USERS ||--o{ ORDERS : user_id", type: 'erDiagram' }
17
- class DiagramGenerator < BaseService
18
- attr_reader :session_id, :diagram_type, :registry, :error_handler, :logger
18
+ # # => { success: true, content: "erDiagram\n...", type: "erDiagram" }
19
+ class DiagramGenerator
20
+ include Dbwatcher::Logging
19
21
 
20
- # Initialize with session id and diagram type
22
+ # Initialize generator with options
21
23
  #
22
- # @param session_id [String] session identifier
24
+ # @param session_id [String] session ID to analyze
23
25
  # @param diagram_type [String] type of diagram to generate
24
- # @param dependencies [Hash] optional dependency injection
25
- # @option dependencies [DiagramTypeRegistry] :registry type registry
26
- # @option dependencies [DiagramErrorHandler] :error_handler error handler
27
- # @option dependencies [Logger] :logger logger instance
28
- def initialize(session_id, diagram_type = "database_tables", dependencies = {})
26
+ # @param options [Hash] additional options
27
+ def initialize(session_id:, diagram_type:, options: {})
29
28
  @session_id = session_id
30
29
  @diagram_type = diagram_type
31
- @registry = dependencies[:registry] || DiagramTypeRegistry.new
32
- @error_handler = dependencies[:error_handler] || DiagramErrorHandler.new
33
- @logger = dependencies[:logger] || default_logger
34
- super()
30
+ @options = options
31
+ @registry = options[:registry] || DiagramTypeRegistry.new
32
+ @logger = options[:logger] || Rails.logger
35
33
  end
36
34
 
37
- # Generate diagram for session
35
+ # Generate diagram
38
36
  #
39
- # @return [Hash] diagram data with content and type
37
+ # @return [Hash] diagram generation result
40
38
  def call
41
- @logger.info("Generating diagram for session #{@session_id} with type #{@diagram_type}")
42
- start_time = Time.now
39
+ log_info("Generating diagram of type #{@diagram_type} for session #{@session_id}")
43
40
 
44
- begin
45
- result = generate_diagram
46
- log_completion(start_time, result)
47
- result
48
- rescue StandardError => e
49
- @error_handler.handle_generation_error(e, error_context)
50
- end
51
- end
41
+ start_time = Time.now
42
+ result = generate_diagram
52
43
 
53
- # Get available diagram types with metadata
54
- #
55
- # @return [Hash] diagram types with metadata
56
- def available_types
57
- @registry.available_types_with_metadata
58
- end
44
+ duration_ms = ((Time.now - start_time) * 1000).round(2)
45
+ log_info("Diagram generation completed in #{duration_ms}ms", {
46
+ session_id: @session_id,
47
+ diagram_type: @diagram_type,
48
+ success: result[:success]
49
+ })
59
50
 
60
- # Get available diagram types (class method for backward compatibility)
61
- #
62
- # @return [Hash] diagram types with metadata
63
- def self.available_types
64
- DiagramTypeRegistry.new.available_types_with_metadata
51
+ result
52
+ rescue StandardError => e
53
+ log_error("Diagram generation failed: #{e.message}", error_context)
54
+ error_result("Diagram generation failed: #{e.message}")
65
55
  end
66
56
 
67
57
  private
68
58
 
69
- # Default logger when no logger is provided
70
- #
71
- # @return [Logger] default logger instance
72
- def default_logger
73
- # Use Rails logger if available, otherwise create a simple logger
74
- if defined?(Rails) && Rails.respond_to?(:logger)
75
- Rails.logger
76
- else
77
- require "logger"
78
- Logger.new($stdout)
79
- end
80
- end
81
-
82
- # Generate diagram using standardized analyzer-to-strategy flow
59
+ # Generate diagram based on configuration
83
60
  #
84
61
  # @return [Hash] diagram generation result
85
62
  def generate_diagram
@@ -96,8 +73,8 @@ module Dbwatcher
96
73
  analyzer = @registry.create_analyzer(@diagram_type, session)
97
74
  dataset = analyzer.call
98
75
 
99
- @logger.debug("Generated dataset with #{dataset.entities.size} entities and " \
100
- "#{dataset.relationships.size} relationships")
76
+ log_debug("Generated dataset with #{dataset.entities.size} entities and " \
77
+ "#{dataset.relationships.size} relationships")
101
78
 
102
79
  # Create strategy and generate diagram from dataset
103
80
  strategy = @registry.create_strategy(@diagram_type)
@@ -110,7 +87,7 @@ module Dbwatcher
110
87
  def load_session
111
88
  Dbwatcher::Storage.sessions.find(@session_id)
112
89
  rescue StandardError => e
113
- @logger.warn("Could not load session #{@session_id}: #{e.message}")
90
+ log_warn("Could not load session #{@session_id}: #{e.message}")
114
91
  nil
115
92
  end
116
93
 
@@ -138,17 +115,6 @@ module Dbwatcher
138
115
  generated_at: Time.now.iso8601
139
116
  }
140
117
  end
141
-
142
- # Log generation completion
143
- #
144
- # @param start_time [Time] operation start time
145
- # @param result [Hash] generation result
146
- def log_completion(start_time, result)
147
- duration = Time.now - start_time
148
- success = result[:success] || false
149
- @logger.info("Diagram generation completed for session #{@session_id} type #{@diagram_type} " \
150
- "in #{(duration * 1000).round(2)}ms - Success: #{success}")
151
- end
152
118
  end
153
119
  end
154
120
  end
@@ -8,6 +8,8 @@ module Dbwatcher
8
8
  # Defines the common interface and shared behavior for all diagram generation
9
9
  # strategies. Subclasses must implement the render_diagram method.
10
10
  class BaseDiagramStrategy
11
+ include Dbwatcher::Logging
12
+
11
13
  attr_reader :syntax_builder, :logger
12
14
 
13
15
  # Initialize strategy with dependencies
@@ -25,8 +27,8 @@ module Dbwatcher
25
27
  # @param dataset [Dataset] standardized dataset
26
28
  # @return [Hash] diagram generation result
27
29
  def generate_from_dataset(dataset)
28
- @logger.info("Generating diagram from dataset with #{dataset.entities.size} entities and " \
29
- "#{dataset.relationships.size} relationships")
30
+ log_info("Generating diagram from dataset with #{dataset.entities.size} entities and " \
31
+ "#{dataset.relationships.size} relationships")
30
32
  start_time = Time.current
31
33
 
32
34
  begin
@@ -40,7 +42,7 @@ module Dbwatcher
40
42
 
41
43
  result
42
44
  rescue StandardError => e
43
- @logger.error("Diagram generation failed: #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}")
45
+ log_error("Diagram generation failed: #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}")
44
46
  error_response("Diagram generation failed: #{e.message}")
45
47
  end
46
48
  end
@@ -59,13 +61,25 @@ module Dbwatcher
59
61
 
60
62
  protected
61
63
 
62
- # Render diagram from dataset (abstract method)
64
+ # Render diagram from dataset (template method)
63
65
  #
64
66
  # @param dataset [Dataset] standardized dataset
65
67
  # @return [Hash] diagram generation result
66
- # @raise [NotImplementedError] if not implemented by subclass
67
68
  def render_diagram(dataset)
68
- raise NotImplementedError, "Subclasses must implement render_diagram method"
69
+ log_debug("Rendering #{mermaid_diagram_type} diagram from dataset with " \
70
+ "#{dataset.entities.size} entities and #{dataset.relationships.size} relationships")
71
+
72
+ # Generate diagram content directly from dataset
73
+ content = generate_diagram_content(dataset)
74
+ success_response(content, mermaid_diagram_type)
75
+ end
76
+
77
+ # Generate diagram content based on dataset (to be implemented by subclasses)
78
+ #
79
+ # @param dataset [Dataset] standardized dataset
80
+ # @return [String] diagram content
81
+ def generate_diagram_content(dataset)
82
+ raise NotImplementedError, "Subclasses must implement generate_diagram_content method"
69
83
  end
70
84
 
71
85
  # Build empty diagram with message
@@ -118,9 +132,9 @@ module Dbwatcher
118
132
  # @param operation [String] operation name
119
133
  # @param duration [Float] operation duration in seconds
120
134
  # @param context [Hash] additional context
121
- def log_operation_completion(operation, duration, _context = {})
122
- @logger.info("Strategy operation completed: #{operation} by #{self.class.name} " \
123
- "in #{(duration * 1000).round(2)}ms")
135
+ def log_operation_completion(operation, duration, context = {})
136
+ log_info("Strategy operation completed: #{operation} by #{self.class.name} " \
137
+ "in #{(duration * 1000).round(2)}ms", context)
124
138
  end
125
139
 
126
140
  # Create default syntax builder
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "standard_diagram_strategy"
4
+
3
5
  module Dbwatcher
4
6
  module Services
5
7
  module DiagramStrategies
@@ -7,31 +9,10 @@ module Dbwatcher
7
9
  #
8
10
  # Handles class diagram generation by converting dataset entities and relationships
9
11
  # to Mermaid class diagram syntax.
10
- class ClassDiagramStrategy < BaseDiagramStrategy
11
- protected
12
-
13
- # Render class diagram from standardized dataset
14
- #
15
- # @param dataset [Dataset] standardized dataset
16
- # @return [Hash] diagram generation result
17
- def render_diagram(dataset)
18
- @logger.debug "Rendering class diagram from dataset with #{dataset.entities.size} entities and " \
19
- "#{dataset.relationships.size} relationships"
20
-
21
- # Generate diagram content directly from dataset
22
- content = if dataset.relationships.empty? && dataset.entities.empty?
23
- @syntax_builder.build_empty_class_diagram("No model associations or entities found")
24
- else
25
- @syntax_builder.build_class_diagram_from_dataset(dataset)
26
- end
27
-
28
- success_response(content, "classDiagram")
29
- end
30
-
12
+ class ClassDiagramStrategy < StandardDiagramStrategy
31
13
  private
32
14
 
33
15
  # Strategy metadata methods
34
-
35
16
  def strategy_name
36
17
  "Model Associations (Class Diagram)"
37
18
  end
@@ -43,6 +24,19 @@ module Dbwatcher
43
24
  def mermaid_diagram_type
44
25
  "classDiagram"
45
26
  end
27
+
28
+ # Diagram generation configuration
29
+ def empty_diagram_method
30
+ :build_empty_class_diagram
31
+ end
32
+
33
+ def empty_diagram_message
34
+ "No model associations or entities found"
35
+ end
36
+
37
+ def full_diagram_method
38
+ :build_class_diagram_from_dataset
39
+ end
46
40
  end
47
41
  end
48
42
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dbwatcher
4
+ module Services
5
+ module DiagramStrategies
6
+ # Helper module for diagram strategies
7
+ #
8
+ # Provides common utility methods for diagram strategies to reduce code duplication.
9
+ # These methods handle common patterns in diagram generation.
10
+ module DiagramStrategyHelpers
11
+ # Generate diagram content with empty state handling
12
+ #
13
+ # @param dataset [Dataset] standardized dataset
14
+ # @param options [Hash] options for diagram generation
15
+ # @option options [Symbol] :empty_method method to call for empty diagram
16
+ # @option options [String] :empty_message message for empty diagram
17
+ # @option options [Symbol] :empty_entities_method method to call for diagram with only entities
18
+ # @option options [Symbol] :full_diagram_method method to call for complete diagram
19
+ # @return [String] diagram content
20
+ def generate_standard_diagram_content(dataset, options)
21
+ if dataset.relationships.empty? && dataset.entities.empty?
22
+ @syntax_builder.send(options[:empty_method], options[:empty_message])
23
+ elsif dataset.relationships.empty? && options[:empty_entities_method]
24
+ # Show isolated entities if no relationships but entities exist
25
+ @syntax_builder.send(options[:empty_entities_method], dataset.entities.values)
26
+ else
27
+ @syntax_builder.send(options[:full_diagram_method], dataset)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "standard_diagram_strategy"
4
+
3
5
  module Dbwatcher
4
6
  module Services
5
7
  module DiagramStrategies
@@ -7,34 +9,10 @@ module Dbwatcher
7
9
  #
8
10
  # Handles ERD diagram generation by converting dataset entities and relationships
9
11
  # to Mermaid ERD syntax.
10
- class ErdDiagramStrategy < BaseDiagramStrategy
11
- protected
12
-
13
- # Render ERD diagram from standardized dataset
14
- #
15
- # @param dataset [Dataset] standardized dataset
16
- # @return [Hash] diagram generation result
17
- def render_diagram(dataset)
18
- @logger.debug "Rendering ERD diagram from dataset with #{dataset.entities.size} entities and " \
19
- "#{dataset.relationships.size} relationships"
20
-
21
- # Generate diagram content directly from dataset
22
- content = if dataset.relationships.empty? && dataset.entities.empty?
23
- @syntax_builder.build_empty_erd("No database relationships or tables found")
24
- elsif dataset.relationships.empty?
25
- # Show isolated tables if no relationships but entities exist
26
- @syntax_builder.build_erd_diagram_with_tables(dataset.entities.values)
27
- else
28
- @syntax_builder.build_erd_diagram_from_dataset(dataset)
29
- end
30
-
31
- success_response(content, "erDiagram")
32
- end
33
-
12
+ class ErdDiagramStrategy < StandardDiagramStrategy
34
13
  private
35
14
 
36
15
  # Strategy metadata methods
37
-
38
16
  def strategy_name
39
17
  "Database Schema (ERD)"
40
18
  end
@@ -46,6 +24,23 @@ module Dbwatcher
46
24
  def mermaid_diagram_type
47
25
  "erDiagram"
48
26
  end
27
+
28
+ # Diagram generation configuration
29
+ def empty_diagram_method
30
+ :build_empty_erd
31
+ end
32
+
33
+ def empty_diagram_message
34
+ "No database relationships or tables found"
35
+ end
36
+
37
+ def empty_entities_method
38
+ :build_erd_diagram_with_tables
39
+ end
40
+
41
+ def full_diagram_method
42
+ :build_erd_diagram_from_dataset
43
+ end
49
44
  end
50
45
  end
51
46
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "standard_diagram_strategy"
4
+
3
5
  module Dbwatcher
4
6
  module Services
5
7
  module DiagramStrategies
@@ -7,34 +9,10 @@ module Dbwatcher
7
9
  #
8
10
  # Handles flowchart diagram generation by converting dataset entities and relationships
9
11
  # to Mermaid flowchart syntax.
10
- class FlowchartDiagramStrategy < BaseDiagramStrategy
11
- protected
12
-
13
- # Render flowchart diagram from standardized dataset
14
- #
15
- # @param dataset [Dataset] standardized dataset
16
- # @return [Hash] diagram generation result
17
- def render_diagram(dataset)
18
- @logger.debug "Rendering flowchart diagram from dataset with #{dataset.entities.size} entities and " \
19
- "#{dataset.relationships.size} relationships"
20
-
21
- # Generate diagram content directly from dataset
22
- content = if dataset.relationships.empty? && dataset.entities.empty?
23
- @syntax_builder.build_empty_flowchart("No model associations or entities found")
24
- elsif dataset.relationships.empty?
25
- # Show isolated nodes if no relationships but entities exist
26
- @syntax_builder.build_flowchart_with_nodes(dataset.entities.values)
27
- else
28
- @syntax_builder.build_flowchart_diagram_from_dataset(dataset)
29
- end
30
-
31
- success_response(content, "flowchart")
32
- end
33
-
12
+ class FlowchartDiagramStrategy < StandardDiagramStrategy
34
13
  private
35
14
 
36
15
  # Strategy metadata methods
37
-
38
16
  def strategy_name
39
17
  "Model Associations"
40
18
  end
@@ -46,6 +24,23 @@ module Dbwatcher
46
24
  def mermaid_diagram_type
47
25
  "flowchart"
48
26
  end
27
+
28
+ # Diagram generation configuration
29
+ def empty_diagram_method
30
+ :build_empty_flowchart
31
+ end
32
+
33
+ def empty_diagram_message
34
+ "No model associations or entities found"
35
+ end
36
+
37
+ def empty_entities_method
38
+ :build_flowchart_with_nodes
39
+ end
40
+
41
+ def full_diagram_method
42
+ :build_flowchart_diagram_from_dataset
43
+ end
49
44
  end
50
45
  end
51
46
  end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_diagram_strategy"
4
+ require_relative "diagram_strategy_helpers"
5
+
6
+ module Dbwatcher
7
+ module Services
8
+ module DiagramStrategies
9
+ # Standard diagram strategy implementation
10
+ #
11
+ # Provides a common implementation for diagram strategies that follow
12
+ # the standard pattern of generating diagrams from datasets.
13
+ # Specific strategies can inherit from this class and provide only
14
+ # the necessary configuration.
15
+ class StandardDiagramStrategy < BaseDiagramStrategy
16
+ include DiagramStrategyHelpers
17
+
18
+ # Initialize with configuration options
19
+ #
20
+ # @param dependencies [Hash] injected dependencies
21
+ # @option dependencies [Object] :syntax_builder Mermaid syntax builder
22
+ # @option dependencies [Logger] :logger logger instance
23
+ def initialize(dependencies = {})
24
+ super
25
+ @diagram_options = diagram_options
26
+ end
27
+
28
+ protected
29
+
30
+ # Generate diagram content from dataset using standard pattern
31
+ #
32
+ # @param dataset [Dataset] standardized dataset
33
+ # @return [String] diagram content
34
+ def generate_diagram_content(dataset)
35
+ generate_standard_diagram_content(dataset, @diagram_options)
36
+ end
37
+
38
+ # Get diagram generation options
39
+ #
40
+ # @return [Hash] diagram options
41
+ def diagram_options
42
+ {
43
+ empty_method: empty_diagram_method,
44
+ empty_message: empty_diagram_message,
45
+ empty_entities_method: empty_entities_method,
46
+ full_diagram_method: full_diagram_method
47
+ }
48
+ end
49
+
50
+ # Get method name for generating empty diagram
51
+ #
52
+ # @return [Symbol] method name
53
+ def empty_diagram_method
54
+ raise NotImplementedError, "Subclasses must implement empty_diagram_method"
55
+ end
56
+
57
+ # Get message for empty diagram
58
+ #
59
+ # @return [String] empty diagram message
60
+ def empty_diagram_message
61
+ "No data available for diagram"
62
+ end
63
+
64
+ # Get method name for generating diagram with only entities
65
+ #
66
+ # @return [Symbol] method name
67
+ def empty_entities_method
68
+ nil
69
+ end
70
+
71
+ # Get method name for generating full diagram
72
+ #
73
+ # @return [Symbol] method name
74
+ def full_diagram_method
75
+ raise NotImplementedError, "Subclasses must implement full_diagram_method"
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -29,6 +29,8 @@ require_relative "diagram_analyzers/model_association_analyzer"
29
29
 
30
30
  # Diagram strategies
31
31
  require_relative "diagram_strategies/base_diagram_strategy"
32
+ require_relative "diagram_strategies/diagram_strategy_helpers"
33
+ require_relative "diagram_strategies/standard_diagram_strategy"
32
34
  require_relative "diagram_strategies/erd_diagram_strategy"
33
35
  require_relative "diagram_strategies/class_diagram_strategy"
34
36
  require_relative "diagram_strategies/flowchart_diagram_strategy"
@@ -41,6 +43,13 @@ module Dbwatcher
41
43
  # Diagram System Module
42
44
  # Provides centralized access to diagram generation capabilities
43
45
  module DiagramSystem
46
+ extend Dbwatcher::Logging
47
+
48
+ # Explicitly set the component name for logging
49
+ def self.component_name
50
+ "DiagramSystem"
51
+ end
52
+
44
53
  # Get available diagram types
45
54
  #
46
55
  # @return [Array<String>] available diagram type names
@@ -54,7 +63,11 @@ module Dbwatcher
54
63
  # @param diagram_type [String] type of diagram to generate
55
64
  # @return [Hash] diagram generation result
56
65
  def self.generate(session_id, diagram_type = "database_tables")
57
- DiagramGenerator.new(session_id, diagram_type).call
66
+ log_debug("Generating diagram of type #{diagram_type} for session #{session_id}")
67
+ generator = DiagramGenerator.new(session_id: session_id, diagram_type: diagram_type)
68
+ result = generator.call
69
+ log_debug("Diagram generation completed with success=#{result[:success]}")
70
+ result
58
71
  end
59
72
 
60
73
  # Check if diagram type is supported
@@ -15,6 +15,8 @@ module Dbwatcher
15
15
  # end
16
16
  # end
17
17
  class BaseBuilder
18
+ include Dbwatcher::Logging
19
+
18
20
  # Initialize a new builder with configuration
19
21
  #
20
22
  # @param config [Hash] configuration options
@@ -15,6 +15,8 @@ module Dbwatcher
15
15
  # content = builder.build_erd_diagram_from_dataset(dataset)
16
16
  # # => "erDiagram\n USERS ||--o{ ORDERS : user_id"
17
17
  class MermaidSyntaxBuilder
18
+ include Dbwatcher::Logging
19
+
18
20
  # Custom error classes
19
21
  class SyntaxValidationError < StandardError; end
20
22
  class UnsupportedDiagramTypeError < StandardError; end
@@ -40,8 +42,8 @@ module Dbwatcher
40
42
  # @param options [Hash] generation options
41
43
  # @return [String] Mermaid ERD syntax
42
44
  def build_erd_diagram_from_dataset(dataset, options = {})
43
- @logger.debug "Building ERD diagram from dataset with #{dataset.entities.size} entities and " \
44
- "#{dataset.relationships.size} relationships"
45
+ log_debug("Building ERD diagram from dataset with #{dataset.entities.size} entities and " \
46
+ "#{dataset.relationships.size} relationships")
45
47
 
46
48
  builder = MermaidSyntax::ErdBuilder.new(@config.merge(options))
47
49
  builder.build_from_dataset(dataset)
@@ -53,8 +55,8 @@ module Dbwatcher
53
55
  # @param options [Hash] generation options
54
56
  # @return [String] Mermaid class diagram syntax
55
57
  def build_class_diagram_from_dataset(dataset, options = {})
56
- @logger.debug "Building class diagram from dataset with #{dataset.entities.size} entities and " \
57
- "#{dataset.relationships.size} relationships"
58
+ log_debug("Building class diagram from dataset with #{dataset.entities.size} entities and " \
59
+ "#{dataset.relationships.size} relationships")
58
60
 
59
61
  builder = MermaidSyntax::ClassDiagramBuilder.new(@config.merge(options))
60
62
  builder.build_from_dataset(dataset)
@@ -66,8 +68,8 @@ module Dbwatcher
66
68
  # @param options [Hash] generation options
67
69
  # @return [String] Mermaid flowchart syntax
68
70
  def build_flowchart_diagram_from_dataset(dataset, options = {})
69
- @logger.debug "Building flowchart diagram from dataset with #{dataset.entities.size} entities and " \
70
- "#{dataset.relationships.size} relationships"
71
+ log_debug("Building flowchart diagram from dataset with #{dataset.entities.size} entities and " \
72
+ "#{dataset.relationships.size} relationships")
71
73
 
72
74
  builder = MermaidSyntax::FlowchartBuilder.new(@config.merge(options))
73
75
  builder.build_from_dataset(dataset)
@@ -125,7 +127,7 @@ module Dbwatcher
125
127
  # @param options [Hash] generation options
126
128
  # @return [String] Mermaid ERD syntax
127
129
  def build_erd_diagram_with_tables(entities, options = {})
128
- @logger.debug "Building ERD diagram with #{entities.size} isolated tables"
130
+ log_debug("Building ERD diagram with #{entities.size} isolated tables")
129
131
 
130
132
  dataset = Dbwatcher::Services::DiagramData::Dataset.new
131
133
  entities.each { |entity| dataset.add_entity(entity) }
@@ -139,7 +141,7 @@ module Dbwatcher
139
141
  # @param options [Hash] generation options
140
142
  # @return [String] Mermaid flowchart syntax
141
143
  def build_flowchart_with_nodes(entities, options = {})
142
- @logger.debug "Building flowchart diagram with #{entities.size} isolated nodes"
144
+ log_debug("Building flowchart diagram with #{entities.size} isolated nodes")
143
145
 
144
146
  dataset = Dbwatcher::Services::DiagramData::Dataset.new
145
147
  entities.each { |entity| dataset.add_entity(entity) }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dbwatcher
4
- VERSION = "1.1.3"
4
+ VERSION = "1.1.4"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dbwatcher
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.3
4
+ version: 1.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Huy Nguyen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-11 00:00:00.000000000 Z
11
+ date: 2025-07-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -266,8 +266,10 @@ files:
266
266
  - lib/dbwatcher/services/diagram_generator.rb
267
267
  - lib/dbwatcher/services/diagram_strategies/base_diagram_strategy.rb
268
268
  - lib/dbwatcher/services/diagram_strategies/class_diagram_strategy.rb
269
+ - lib/dbwatcher/services/diagram_strategies/diagram_strategy_helpers.rb
269
270
  - lib/dbwatcher/services/diagram_strategies/erd_diagram_strategy.rb
270
271
  - lib/dbwatcher/services/diagram_strategies/flowchart_diagram_strategy.rb
272
+ - lib/dbwatcher/services/diagram_strategies/standard_diagram_strategy.rb
271
273
  - lib/dbwatcher/services/diagram_system.rb
272
274
  - lib/dbwatcher/services/diagram_type_registry.rb
273
275
  - lib/dbwatcher/services/mermaid_syntax/base_builder.rb