rails_console_pro 0.1.0

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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +12 -0
  3. data/.rspec +4 -0
  4. data/.rspec_status +240 -0
  5. data/.rubocop.yml +26 -0
  6. data/CHANGELOG.md +24 -0
  7. data/CONTRIBUTING.md +76 -0
  8. data/LICENSE.txt +22 -0
  9. data/QUICK_START.md +112 -0
  10. data/README.md +124 -0
  11. data/Rakefile +13 -0
  12. data/config/database.yml +3 -0
  13. data/docs/ASSOCIATION_NAVIGATION.md +85 -0
  14. data/docs/EXPORT.md +95 -0
  15. data/docs/FORMATTING.md +86 -0
  16. data/docs/MODEL_STATISTICS.md +72 -0
  17. data/docs/OBJECT_DIFFING.md +87 -0
  18. data/docs/SCHEMA_INSPECTION.md +60 -0
  19. data/docs/SQL_EXPLAIN.md +70 -0
  20. data/lib/generators/rails_console_pro/install_generator.rb +16 -0
  21. data/lib/generators/rails_console_pro/templates/rails_console_pro.rb +44 -0
  22. data/lib/rails_console_pro/active_record_extensions.rb +113 -0
  23. data/lib/rails_console_pro/association_navigator.rb +273 -0
  24. data/lib/rails_console_pro/base_printer.rb +74 -0
  25. data/lib/rails_console_pro/color_helper.rb +36 -0
  26. data/lib/rails_console_pro/commands/base_command.rb +17 -0
  27. data/lib/rails_console_pro/commands/diff_command.rb +135 -0
  28. data/lib/rails_console_pro/commands/explain_command.rb +118 -0
  29. data/lib/rails_console_pro/commands/export_command.rb +16 -0
  30. data/lib/rails_console_pro/commands/schema_command.rb +20 -0
  31. data/lib/rails_console_pro/commands/stats_command.rb +93 -0
  32. data/lib/rails_console_pro/commands.rb +34 -0
  33. data/lib/rails_console_pro/configuration.rb +219 -0
  34. data/lib/rails_console_pro/diff_result.rb +56 -0
  35. data/lib/rails_console_pro/error_handler.rb +60 -0
  36. data/lib/rails_console_pro/explain_result.rb +47 -0
  37. data/lib/rails_console_pro/format_exporter.rb +403 -0
  38. data/lib/rails_console_pro/global_methods.rb +42 -0
  39. data/lib/rails_console_pro/initializer.rb +176 -0
  40. data/lib/rails_console_pro/model_validator.rb +219 -0
  41. data/lib/rails_console_pro/paginator.rb +204 -0
  42. data/lib/rails_console_pro/printers/active_record_printer.rb +30 -0
  43. data/lib/rails_console_pro/printers/collection_printer.rb +34 -0
  44. data/lib/rails_console_pro/printers/diff_printer.rb +97 -0
  45. data/lib/rails_console_pro/printers/explain_printer.rb +151 -0
  46. data/lib/rails_console_pro/printers/relation_printer.rb +25 -0
  47. data/lib/rails_console_pro/printers/schema_printer.rb +188 -0
  48. data/lib/rails_console_pro/printers/stats_printer.rb +129 -0
  49. data/lib/rails_console_pro/pry_commands.rb +241 -0
  50. data/lib/rails_console_pro/pry_integration.rb +9 -0
  51. data/lib/rails_console_pro/railtie.rb +29 -0
  52. data/lib/rails_console_pro/schema_inspector_result.rb +43 -0
  53. data/lib/rails_console_pro/serializers/active_record_serializer.rb +18 -0
  54. data/lib/rails_console_pro/serializers/array_serializer.rb +31 -0
  55. data/lib/rails_console_pro/serializers/base_serializer.rb +25 -0
  56. data/lib/rails_console_pro/serializers/diff_serializer.rb +24 -0
  57. data/lib/rails_console_pro/serializers/explain_serializer.rb +35 -0
  58. data/lib/rails_console_pro/serializers/relation_serializer.rb +25 -0
  59. data/lib/rails_console_pro/serializers/schema_serializer.rb +121 -0
  60. data/lib/rails_console_pro/serializers/stats_serializer.rb +24 -0
  61. data/lib/rails_console_pro/services/column_stats_calculator.rb +64 -0
  62. data/lib/rails_console_pro/services/index_analyzer.rb +110 -0
  63. data/lib/rails_console_pro/services/stats_calculator.rb +40 -0
  64. data/lib/rails_console_pro/services/table_size_calculator.rb +43 -0
  65. data/lib/rails_console_pro/stats_result.rb +66 -0
  66. data/lib/rails_console_pro/version.rb +6 -0
  67. data/lib/rails_console_pro.rb +14 -0
  68. data/lib/tasks/rails_console_pro.rake +10 -0
  69. data/rails_console_pro.gemspec +60 -0
  70. metadata +240 -0
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsConsolePro
4
+ module Commands
5
+ class ExplainCommand < BaseCommand
6
+ def execute(relation_or_model, *args)
7
+ relation = build_relation(relation_or_model, *args)
8
+ return nil unless relation
9
+
10
+ execute_explain(relation)
11
+ rescue => e
12
+ RailsConsolePro::ErrorHandler.handle(e, context: :explain)
13
+ end
14
+
15
+ private
16
+
17
+ def build_relation(relation_or_model, *args)
18
+ if ModelValidator.valid_model?(relation_or_model)
19
+ args.empty? ? relation_or_model.all : relation_or_model.where(*args)
20
+ elsif relation_or_model.respond_to?(:to_sql)
21
+ relation_or_model
22
+ else
23
+ puts pastel.red("Error: Cannot explain #{relation_or_model.class}")
24
+ nil
25
+ end
26
+ end
27
+
28
+ def execute_explain(relation)
29
+ sql = relation.to_sql
30
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
31
+
32
+ explain_output = fetch_explain_output(sql)
33
+ relation.load
34
+ execution_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - start_time
35
+
36
+ indexes_used, recommendations = analyze_explain_output(explain_output, sql, execution_time)
37
+ statistics = build_statistics(relation)
38
+
39
+ ExplainResult.new(
40
+ sql: sql,
41
+ explain_output: explain_output,
42
+ execution_time: execution_time,
43
+ indexes_used: indexes_used,
44
+ recommendations: recommendations,
45
+ statistics: statistics
46
+ )
47
+ end
48
+
49
+ def fetch_explain_output(sql)
50
+ connection = ActiveRecord::Base.connection
51
+ adapter_name = connection.adapter_name.downcase
52
+
53
+ if adapter_name.include?('postgresql')
54
+ explain_sql = "EXPLAIN (ANALYZE, BUFFERS, VERBOSE) #{sql}"
55
+ raw_explain = connection.execute(explain_sql)
56
+ raw_explain.values.flatten.join("\n")
57
+ elsif adapter_name.include?('mysql')
58
+ explain_sql = "EXPLAIN #{sql}"
59
+ connection.execute(explain_sql).to_a
60
+ else
61
+ connection.exec_query("EXPLAIN #{sql}").to_a
62
+ end
63
+ end
64
+
65
+ def analyze_explain_output(explain_output, sql, execution_time)
66
+ indexes_used = []
67
+ recommendations = []
68
+
69
+ case explain_output
70
+ when String
71
+ analyze_postgresql_output(explain_output, recommendations, indexes_used)
72
+ when Array
73
+ analyze_mysql_output(explain_output, recommendations, indexes_used)
74
+ end
75
+
76
+ add_performance_recommendations(sql, execution_time, recommendations)
77
+ [indexes_used, recommendations]
78
+ end
79
+
80
+ def analyze_postgresql_output(explain_output, recommendations, indexes_used)
81
+ recommendations << "Sequential scan detected - consider adding an index" if explain_output.include?("Seq Scan")
82
+
83
+ explain_output.scan(/Index (?:Scan|Only Scan) using (\w+)/) do |match|
84
+ indexes_used << match[0]
85
+ end
86
+ end
87
+
88
+ def analyze_mysql_output(explain_output, recommendations, indexes_used)
89
+ explain_output.each do |row|
90
+ next unless row.is_a?(Hash)
91
+
92
+ key = row['key'] || row[:key]
93
+ indexes_used << key if key
94
+
95
+ type = (row['type'] || row[:type]).to_s.downcase
96
+ if type == 'all'
97
+ table = row['table'] || row[:table]
98
+ recommendations << "Full table scan on #{table} - consider adding an index"
99
+ end
100
+ end
101
+ end
102
+
103
+ def add_performance_recommendations(sql, execution_time, recommendations)
104
+ recommendations << "Query took over 100ms - consider optimization" if execution_time > 100
105
+ recommendations << "LIKE query detected - ensure you're not using leading wildcards (%value)" if sql.downcase.include?('like')
106
+ end
107
+
108
+ def build_statistics(relation)
109
+ {
110
+ "Total Rows" => relation.count,
111
+ "Tables Involved" => relation.klass.table_name,
112
+ "Database Adapter" => ActiveRecord::Base.connection.adapter_name.capitalize
113
+ }
114
+ end
115
+ end
116
+ end
117
+ end
118
+
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsConsolePro
4
+ module Commands
5
+ class ExportCommand < BaseCommand
6
+ def execute(data, file_path, format: nil)
7
+ return nil if data.nil?
8
+
9
+ FormatExporter.export_to_file(data, file_path, format: format)
10
+ rescue => e
11
+ RailsConsolePro::ErrorHandler.handle(e, context: :export)
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsConsolePro
4
+ module Commands
5
+ class SchemaCommand < BaseCommand
6
+ def execute(model_class)
7
+ error_message = ModelValidator.validate_for_schema(model_class)
8
+ if error_message
9
+ puts pastel.red("Error: #{error_message}")
10
+ return nil
11
+ end
12
+
13
+ SchemaInspectorResult.new(model_class)
14
+ rescue => e
15
+ RailsConsolePro::ErrorHandler.handle(e, context: :schema)
16
+ end
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsConsolePro
4
+ module Commands
5
+ class StatsCommand < BaseCommand
6
+ def execute(model_class)
7
+ return nil if model_class.nil?
8
+
9
+ error_message = ModelValidator.validate_for_stats(model_class)
10
+ if error_message
11
+ puts pastel.red("Error: #{error_message}")
12
+ return nil
13
+ end
14
+
15
+ execute_stats(model_class)
16
+ rescue => e
17
+ RailsConsolePro::ErrorHandler.handle(e, context: :stats)
18
+ end
19
+
20
+ private
21
+
22
+ def execute_stats(model_class)
23
+ connection = model_class.connection
24
+ # Get table name directly since model is already validated
25
+ table_name = begin
26
+ model_class.table_name
27
+ rescue => e
28
+ # If we can't get table name, we can't calculate all stats
29
+ puts pastel.yellow("Warning: Could not get table name for #{model_class.name}: #{e.message}")
30
+ return nil
31
+ end
32
+
33
+ # Double-check table exists (defensive check)
34
+ unless ModelValidator.has_table?(model_class)
35
+ puts pastel.yellow("Warning: Table does not exist for #{model_class.name}")
36
+ return nil
37
+ end
38
+
39
+ # Record count (safe with error handling)
40
+ record_count = safe_count(model_class)
41
+ safe_count_proc = -> { safe_count(model_class) }
42
+
43
+ # Growth rate (only if created_at exists)
44
+ growth_rate = if ModelValidator.has_timestamp_column?(model_class)
45
+ Services::StatsCalculator.calculate_growth_rate(model_class, safe_count_proc)
46
+ else
47
+ nil
48
+ end
49
+
50
+ # Table size (database-specific)
51
+ table_size = Services::TableSizeCalculator.calculate(connection, table_name)
52
+
53
+ # Index usage (safe with error handling)
54
+ index_usage = Services::IndexAnalyzer.analyze(connection, table_name)
55
+
56
+ # Column statistics (only for smaller tables)
57
+ column_stats = if ModelValidator.large_table?(model_class)
58
+ {} # Skip for large tables to avoid performance issues
59
+ else
60
+ Services::ColumnStatsCalculator.calculate(
61
+ model_class,
62
+ connection,
63
+ table_name,
64
+ safe_count_proc,
65
+ config
66
+ )
67
+ end
68
+
69
+ StatsResult.new(
70
+ model: model_class,
71
+ record_count: record_count,
72
+ growth_rate: growth_rate,
73
+ table_size: table_size,
74
+ index_usage: index_usage,
75
+ column_stats: column_stats
76
+ )
77
+ end
78
+
79
+ def safe_count(model_class)
80
+ model_class.count
81
+ rescue ActiveRecord::StatementInvalid => e
82
+ raise e # Re-raise StatementInvalid so it can be caught by stats method
83
+ rescue => e
84
+ 0 # Return 0 for other errors
85
+ end
86
+
87
+ def config
88
+ RailsConsolePro.config
89
+ end
90
+ end
91
+ end
92
+ end
93
+
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsConsolePro
4
+ # Command methods for schema inspection and SQL explain
5
+ # Thin facade that delegates to command classes
6
+ module Commands
7
+ extend self
8
+
9
+ # Schema inspection command
10
+ def schema(model_class)
11
+ SchemaCommand.new.execute(model_class)
12
+ end
13
+
14
+ # SQL explain command
15
+ def explain(relation_or_model, *args)
16
+ ExplainCommand.new.execute(relation_or_model, *args)
17
+ end
18
+
19
+ # Export data to file (works with any exportable object)
20
+ def export(data, file_path, format: nil)
21
+ ExportCommand.new.execute(data, file_path, format: format)
22
+ end
23
+
24
+ # Model statistics command
25
+ def stats(model_class)
26
+ StatsCommand.new.execute(model_class)
27
+ end
28
+
29
+ # Object comparison command
30
+ def diff(object1, object2)
31
+ DiffCommand.new.execute(object1, object2)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,219 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsConsolePro
4
+ # Configuration class for Enhanced Console Printer
5
+ class Configuration
6
+ # Color scheme presets
7
+ COLOR_SCHEMES = {
8
+ dark: {
9
+ header: :bright_blue,
10
+ footer: :bright_blue,
11
+ border: :dim,
12
+ attribute_key: :blue,
13
+ attribute_value_nil: :white,
14
+ attribute_value_numeric: :bright_blue,
15
+ attribute_value_boolean: :green,
16
+ attribute_value_time: :blue,
17
+ attribute_value_string: :white,
18
+ error: :red,
19
+ success: :green,
20
+ warning: :yellow,
21
+ info: :cyan
22
+ },
23
+ light: {
24
+ header: :bright_cyan,
25
+ footer: :bright_cyan,
26
+ border: :dim,
27
+ attribute_key: :bright_blue,
28
+ attribute_value_nil: :black,
29
+ attribute_value_numeric: :blue,
30
+ attribute_value_boolean: :green,
31
+ attribute_value_time: :cyan,
32
+ attribute_value_string: :black,
33
+ error: :red,
34
+ success: :green,
35
+ warning: :yellow,
36
+ info: :cyan
37
+ },
38
+ custom: {} # Will be populated by user
39
+ }.freeze
40
+
41
+ # Feature toggles
42
+ attr_accessor :enabled
43
+ attr_accessor :schema_command_enabled
44
+ attr_accessor :explain_command_enabled
45
+ attr_accessor :navigate_command_enabled
46
+ attr_accessor :stats_command_enabled
47
+ attr_accessor :diff_command_enabled
48
+ attr_accessor :active_record_printer_enabled
49
+ attr_accessor :relation_printer_enabled
50
+ attr_accessor :collection_printer_enabled
51
+ attr_accessor :export_enabled
52
+
53
+ # Color customization
54
+ attr_accessor :color_scheme
55
+ attr_accessor :colors
56
+
57
+ # Style customization
58
+ attr_accessor :max_depth
59
+ attr_accessor :show_sql_by_default
60
+ attr_accessor :show_welcome_message
61
+ attr_accessor :border_char
62
+ attr_accessor :header_width
63
+
64
+ # Type-specific colors (for schema printer)
65
+ attr_accessor :type_colors
66
+
67
+ # Validator colors (for schema printer)
68
+ attr_accessor :validator_colors
69
+
70
+ # Pagination settings
71
+ attr_accessor :pagination_enabled
72
+ attr_accessor :pagination_threshold
73
+ attr_accessor :pagination_page_size
74
+
75
+ # Stats calculation settings
76
+ attr_accessor :stats_large_table_threshold
77
+ attr_accessor :stats_skip_distinct_threshold
78
+
79
+ def initialize
80
+ # Default feature toggles - all enabled
81
+ @enabled = true
82
+ @schema_command_enabled = true
83
+ @explain_command_enabled = true
84
+ @navigate_command_enabled = true
85
+ @stats_command_enabled = true
86
+ @diff_command_enabled = true
87
+ @active_record_printer_enabled = true
88
+ @relation_printer_enabled = true
89
+ @collection_printer_enabled = true
90
+ @export_enabled = true
91
+
92
+ # Default color scheme
93
+ @color_scheme = :dark
94
+ @colors = COLOR_SCHEMES[:dark].dup
95
+
96
+ # Default style options
97
+ @max_depth = 10
98
+ @show_sql_by_default = false
99
+ @show_welcome_message = true
100
+ @border_char = "─"
101
+ @header_width = 60
102
+
103
+ # Default type colors
104
+ @type_colors = {
105
+ integer: :bright_blue,
106
+ bigint: :bright_blue,
107
+ decimal: :bright_blue,
108
+ float: :bright_blue,
109
+ string: :green,
110
+ text: :green,
111
+ datetime: :cyan,
112
+ timestamp: :cyan,
113
+ date: :cyan,
114
+ time: :cyan,
115
+ boolean: :magenta,
116
+ json: :yellow,
117
+ jsonb: :yellow
118
+ }
119
+
120
+ # Default validator colors
121
+ @validator_colors = {
122
+ 'PresenceValidator' => :red,
123
+ 'UniquenessValidator' => :magenta,
124
+ 'LengthValidator' => :cyan,
125
+ 'FormatValidator' => :yellow,
126
+ 'NumericalityValidator' => :blue,
127
+ 'InclusionValidator' => :green,
128
+ 'ExclusionValidator' => :red,
129
+ 'ConfirmationValidator' => :cyan,
130
+ 'AcceptanceValidator' => :green
131
+ }
132
+
133
+ # Default pagination settings
134
+ @pagination_enabled = true
135
+ @pagination_threshold = 10 # Automatically paginate collections with 10+ items
136
+ @pagination_page_size = 5 # Show 5 records per page
137
+
138
+ # Default stats calculation settings
139
+ @stats_large_table_threshold = 10_000 # Consider table large if it has 10k+ records
140
+ @stats_skip_distinct_threshold = 10_000 # Skip distinct count for tables with 10k+ records
141
+ end
142
+
143
+ # Set color scheme (dark, light, or custom)
144
+ def color_scheme=(scheme)
145
+ @color_scheme = scheme.to_sym
146
+ if COLOR_SCHEMES.key?(@color_scheme) && @color_scheme != :custom
147
+ @colors = COLOR_SCHEMES[@color_scheme].dup
148
+ end
149
+ end
150
+
151
+ # Set a custom color
152
+ def set_color(key, value)
153
+ @colors = @colors.dup unless @colors.frozen?
154
+ @colors[key.to_sym] = value.to_sym
155
+ @color_scheme = :custom
156
+ end
157
+
158
+ # Get a color value
159
+ def get_color(key)
160
+ @colors[key.to_sym] || :white
161
+ end
162
+
163
+ # Set type color
164
+ def set_type_color(type, color)
165
+ @type_colors = @type_colors.dup if @type_colors.frozen?
166
+ @type_colors[type.to_sym] = color.to_sym
167
+ end
168
+
169
+ # Get type color
170
+ def get_type_color(type)
171
+ @type_colors[type.to_sym] || :white
172
+ end
173
+
174
+ # Set validator color
175
+ def set_validator_color(validator_type, color)
176
+ @validator_colors = @validator_colors.dup if @validator_colors.frozen?
177
+ @validator_colors[validator_type.to_s] = color.to_sym
178
+ end
179
+
180
+ # Get validator color
181
+ def get_validator_color(validator_type)
182
+ @validator_colors[validator_type.to_s] || :white
183
+ end
184
+
185
+ # Disable all features
186
+ def disable_all
187
+ @enabled = false
188
+ @schema_command_enabled = false
189
+ @explain_command_enabled = false
190
+ @navigate_command_enabled = false
191
+ @stats_command_enabled = false
192
+ @diff_command_enabled = false
193
+ @active_record_printer_enabled = false
194
+ @relation_printer_enabled = false
195
+ @collection_printer_enabled = false
196
+ @export_enabled = false
197
+ end
198
+
199
+ # Enable all features
200
+ def enable_all
201
+ @enabled = true
202
+ @schema_command_enabled = true
203
+ @explain_command_enabled = true
204
+ @navigate_command_enabled = true
205
+ @stats_command_enabled = true
206
+ @diff_command_enabled = true
207
+ @active_record_printer_enabled = true
208
+ @relation_printer_enabled = true
209
+ @collection_printer_enabled = true
210
+ @export_enabled = true
211
+ end
212
+
213
+ # Reset to defaults
214
+ def reset
215
+ initialize
216
+ end
217
+ end
218
+ end
219
+
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsConsolePro
4
+ # Value object for object comparison results
5
+ class DiffResult
6
+ attr_reader :object1, :object2, :differences, :identical, :object1_type,
7
+ :object2_type, :timestamp
8
+
9
+ def initialize(object1:, object2:, differences: {}, identical: false,
10
+ object1_type: nil, object2_type: nil, timestamp: Time.current)
11
+ @object1 = object1
12
+ @object2 = object2
13
+ @differences = differences
14
+ @identical = identical
15
+ @object1_type = object1_type || object1.class.name
16
+ @object2_type = object2_type || object2.class.name
17
+ @timestamp = timestamp
18
+ end
19
+
20
+ def ==(other)
21
+ other.is_a?(self.class) && other.object1 == object1 && other.object2 == object2
22
+ end
23
+
24
+ def has_differences?
25
+ !identical && differences.any?
26
+ end
27
+
28
+ def different_types?
29
+ object1_type != object2_type
30
+ end
31
+
32
+ def diff_count
33
+ differences.size
34
+ end
35
+
36
+ # Export to JSON
37
+ def to_json(pretty: true)
38
+ FormatExporter.to_json(self, pretty: pretty)
39
+ end
40
+
41
+ # Export to YAML
42
+ def to_yaml
43
+ FormatExporter.to_yaml(self)
44
+ end
45
+
46
+ # Export to HTML
47
+ def to_html(style: :default)
48
+ FormatExporter.to_html(self, title: "Diff Comparison", style: style)
49
+ end
50
+
51
+ # Export to file
52
+ def export_to_file(file_path, format: nil)
53
+ FormatExporter.export_to_file(self, file_path, format: format)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsConsolePro
4
+ # Unified error handler for consistent error handling across the gem
5
+ module ErrorHandler
6
+ extend self
7
+ include ColorHelper
8
+
9
+ def handle(error, context: nil)
10
+ case error
11
+ when ActiveRecord::ConfigurationError
12
+ handle_configuration_error(error, context)
13
+ when ActiveRecord::StatementInvalid
14
+ handle_sql_error(error, context)
15
+ when ArgumentError
16
+ handle_argument_error(error, context)
17
+ when NameError
18
+ handle_name_error(error, context)
19
+ else
20
+ handle_generic_error(error, context)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def handle_configuration_error(error, context)
27
+ puts pastel.red.bold("❌ Configuration Error: #{error.message}")
28
+ puts pastel.yellow("💡 Tip: Check that all associations exist in your models")
29
+ nil
30
+ end
31
+
32
+ def handle_sql_error(error, context)
33
+ puts pastel.red.bold("❌ SQL Error: #{error.message}")
34
+ puts pastel.yellow("💡 Tip: Check your query syntax and table/column names")
35
+ nil
36
+ end
37
+
38
+ def handle_argument_error(error, context)
39
+ puts pastel.red.bold("❌ Error: #{error.message}")
40
+ nil
41
+ end
42
+
43
+ def handle_name_error(error, context)
44
+ message = error.message.include?('uninitialized constant') ?
45
+ "Could not find model or class" : error.message
46
+ puts pastel.red.bold("❌ Error: #{message}")
47
+ puts pastel.yellow("💡 Tip: Make sure the model name is correct and loaded")
48
+ nil
49
+ end
50
+
51
+ def handle_generic_error(error, context)
52
+ puts pastel.red.bold("❌ Error: #{error.message}")
53
+ if Rails.env.development? || ENV['RAILS_CONSOLE_PRO_DEBUG']
54
+ puts pastel.dim(error.backtrace.first(3).join("\n"))
55
+ end
56
+ nil
57
+ end
58
+ end
59
+ end
60
+
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsConsolePro
4
+ # Value object for SQL explain results
5
+ class ExplainResult
6
+ attr_reader :sql, :explain_output, :execution_time, :indexes_used,
7
+ :recommendations, :statistics
8
+
9
+ def initialize(sql:, explain_output:, execution_time: nil,
10
+ indexes_used: [], recommendations: [], statistics: nil)
11
+ @sql = sql
12
+ @explain_output = explain_output
13
+ @execution_time = execution_time
14
+ @indexes_used = Array(indexes_used)
15
+ @recommendations = Array(recommendations)
16
+ @statistics = statistics || {}
17
+ end
18
+
19
+ def slow_query?
20
+ execution_time && execution_time > 100
21
+ end
22
+
23
+ def has_indexes?
24
+ indexes_used.any?
25
+ end
26
+
27
+ # Export to JSON
28
+ def to_json(pretty: true)
29
+ FormatExporter.to_json(self, pretty: pretty)
30
+ end
31
+
32
+ # Export to YAML
33
+ def to_yaml
34
+ FormatExporter.to_yaml(self)
35
+ end
36
+
37
+ # Export to HTML
38
+ def to_html(style: :default)
39
+ FormatExporter.to_html(self, title: "SQL Explain Analysis", style: style)
40
+ end
41
+
42
+ # Export to file
43
+ def export_to_file(file_path, format: nil)
44
+ FormatExporter.export_to_file(self, file_path, format: format)
45
+ end
46
+ end
47
+ end