rails_console_pro 0.1.2 → 0.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec_status +288 -240
  3. data/CHANGELOG.md +7 -0
  4. data/QUICK_START.md +17 -0
  5. data/README.md +43 -0
  6. data/docs/FORMATTING.md +5 -0
  7. data/docs/MODEL_INTROSPECTION.md +371 -0
  8. data/docs/MODEL_STATISTICS.md +4 -0
  9. data/docs/OBJECT_DIFFING.md +6 -0
  10. data/docs/PROFILING.md +91 -0
  11. data/docs/QUERY_BUILDER.md +385 -0
  12. data/docs/QUEUE_INSIGHTS.md +82 -0
  13. data/docs/SCHEMA_INSPECTION.md +5 -0
  14. data/docs/SNIPPETS.md +71 -0
  15. data/lib/rails_console_pro/commands/compare_command.rb +151 -0
  16. data/lib/rails_console_pro/commands/introspect_command.rb +220 -0
  17. data/lib/rails_console_pro/commands/jobs_command.rb +212 -0
  18. data/lib/rails_console_pro/commands/profile_command.rb +84 -0
  19. data/lib/rails_console_pro/commands/query_builder_command.rb +43 -0
  20. data/lib/rails_console_pro/commands/snippets_command.rb +141 -0
  21. data/lib/rails_console_pro/commands.rb +30 -0
  22. data/lib/rails_console_pro/compare_result.rb +81 -0
  23. data/lib/rails_console_pro/configuration.rb +51 -0
  24. data/lib/rails_console_pro/format_exporter.rb +32 -0
  25. data/lib/rails_console_pro/global_methods.rb +24 -0
  26. data/lib/rails_console_pro/initializer.rb +41 -1
  27. data/lib/rails_console_pro/introspect_result.rb +101 -0
  28. data/lib/rails_console_pro/model_validator.rb +1 -1
  29. data/lib/rails_console_pro/printers/compare_printer.rb +138 -0
  30. data/lib/rails_console_pro/printers/introspect_printer.rb +282 -0
  31. data/lib/rails_console_pro/printers/profile_printer.rb +180 -0
  32. data/lib/rails_console_pro/printers/query_builder_printer.rb +81 -0
  33. data/lib/rails_console_pro/printers/queue_insights_printer.rb +150 -0
  34. data/lib/rails_console_pro/printers/snippet_collection_printer.rb +68 -0
  35. data/lib/rails_console_pro/printers/snippet_printer.rb +64 -0
  36. data/lib/rails_console_pro/profile_result.rb +109 -0
  37. data/lib/rails_console_pro/pry_commands.rb +106 -0
  38. data/lib/rails_console_pro/query_builder.rb +197 -0
  39. data/lib/rails_console_pro/query_builder_result.rb +66 -0
  40. data/lib/rails_console_pro/queue_insights_result.rb +110 -0
  41. data/lib/rails_console_pro/serializers/compare_serializer.rb +66 -0
  42. data/lib/rails_console_pro/serializers/introspect_serializer.rb +99 -0
  43. data/lib/rails_console_pro/serializers/profile_serializer.rb +73 -0
  44. data/lib/rails_console_pro/serializers/query_builder_serializer.rb +35 -0
  45. data/lib/rails_console_pro/services/introspection_collector.rb +420 -0
  46. data/lib/rails_console_pro/services/profile_collector.rb +245 -0
  47. data/lib/rails_console_pro/services/queue_action_service.rb +176 -0
  48. data/lib/rails_console_pro/services/queue_insight_fetcher.rb +600 -0
  49. data/lib/rails_console_pro/services/snippet_repository.rb +191 -0
  50. data/lib/rails_console_pro/snippets/collection_result.rb +45 -0
  51. data/lib/rails_console_pro/snippets/single_result.rb +30 -0
  52. data/lib/rails_console_pro/snippets/snippet.rb +112 -0
  53. data/lib/rails_console_pro/snippets.rb +13 -0
  54. data/lib/rails_console_pro/version.rb +1 -1
  55. data/rails_console_pro.gemspec +1 -1
  56. metadata +42 -8
@@ -30,5 +30,35 @@ module RailsConsolePro
30
30
  def diff(object1, object2)
31
31
  DiffCommand.new.execute(object1, object2)
32
32
  end
33
+
34
+ # Profiling command
35
+ def profile(target = nil, *args, **kwargs, &block)
36
+ ProfileCommand.new.execute(target, *args, **kwargs, &block)
37
+ end
38
+
39
+ # Queue insights command
40
+ def jobs(options = {})
41
+ JobsCommand.new.execute(options)
42
+ end
43
+
44
+ # Snippets command
45
+ def snippets(action = :list, *args, **kwargs, &block)
46
+ SnippetsCommand.new.execute(action, *args, **kwargs, &block)
47
+ end
48
+
49
+ # Model introspection command
50
+ def introspect(model_class, *options)
51
+ IntrospectCommand.new.execute(model_class, *options)
52
+ end
53
+
54
+ # Query comparison command
55
+ def compare(&block)
56
+ CompareCommand.new.execute(&block)
57
+ end
58
+
59
+ # Query builder command
60
+ def query(model_class, &block)
61
+ QueryBuilderCommand.new.execute(model_class, &block)
62
+ end
33
63
  end
34
64
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsConsolePro
4
+ # Value object for query comparison results
5
+ class CompareResult
6
+ Comparison = Struct.new(
7
+ :name,
8
+ :duration_ms,
9
+ :query_count,
10
+ :result,
11
+ :error,
12
+ :sql_queries,
13
+ :memory_usage_kb,
14
+ keyword_init: true
15
+ )
16
+
17
+ attr_reader :comparisons, :winner, :timestamp
18
+
19
+ def initialize(comparisons:, winner: nil, timestamp: Time.current)
20
+ @comparisons = Array(comparisons)
21
+ @winner = winner
22
+ @timestamp = timestamp
23
+ end
24
+
25
+ def fastest
26
+ comparisons.min_by { |c| c.duration_ms || Float::INFINITY }
27
+ end
28
+
29
+ def slowest
30
+ comparisons.max_by { |c| c.duration_ms || 0 }
31
+ end
32
+
33
+ def has_errors?
34
+ comparisons.any? { |c| c.error }
35
+ end
36
+
37
+ def error_count
38
+ comparisons.count { |c| c.error }
39
+ end
40
+
41
+ def total_queries
42
+ comparisons.sum { |c| c.query_count || 0 }
43
+ end
44
+
45
+ def fastest_name
46
+ fastest&.name
47
+ end
48
+
49
+ def slowest_name
50
+ slowest&.name
51
+ end
52
+
53
+ def performance_ratio
54
+ return nil if comparisons.size < 2 || fastest.nil? || slowest.nil?
55
+ return nil if fastest.duration_ms.nil? || slowest.duration_ms.nil? || fastest.duration_ms.zero?
56
+
57
+ (slowest.duration_ms / fastest.duration_ms).round(2)
58
+ end
59
+
60
+ # Export to JSON
61
+ def to_json(pretty: true)
62
+ FormatExporter.to_json(self, pretty: pretty)
63
+ end
64
+
65
+ # Export to YAML
66
+ def to_yaml
67
+ FormatExporter.to_yaml(self)
68
+ end
69
+
70
+ # Export to HTML
71
+ def to_html(style: :default)
72
+ FormatExporter.to_html(self, title: "Query Comparison", style: style)
73
+ end
74
+
75
+ # Export to file
76
+ def export_to_file(file_path, format: nil)
77
+ FormatExporter.export_to_file(self, file_path, format: format)
78
+ end
79
+ end
80
+ end
81
+
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'tmpdir'
4
+ require 'pathname'
5
+
3
6
  module RailsConsolePro
4
7
  # Configuration class for Enhanced Console Printer
5
8
  class Configuration
@@ -45,10 +48,17 @@ module RailsConsolePro
45
48
  attr_accessor :navigate_command_enabled
46
49
  attr_accessor :stats_command_enabled
47
50
  attr_accessor :diff_command_enabled
51
+ attr_accessor :snippets_command_enabled
52
+ attr_accessor :profile_command_enabled
53
+ attr_accessor :queue_command_enabled
54
+ attr_accessor :introspect_command_enabled
55
+ attr_accessor :compare_command_enabled
56
+ attr_accessor :query_builder_command_enabled
48
57
  attr_accessor :active_record_printer_enabled
49
58
  attr_accessor :relation_printer_enabled
50
59
  attr_accessor :collection_printer_enabled
51
60
  attr_accessor :export_enabled
61
+ attr_accessor :snippet_store_path
52
62
 
53
63
  # Color customization
54
64
  attr_accessor :color_scheme
@@ -76,6 +86,11 @@ module RailsConsolePro
76
86
  attr_accessor :stats_large_table_threshold
77
87
  attr_accessor :stats_skip_distinct_threshold
78
88
 
89
+ # Profiling settings
90
+ attr_accessor :profile_slow_query_threshold
91
+ attr_accessor :profile_duplicate_query_threshold
92
+ attr_accessor :profile_max_saved_queries
93
+
79
94
  def initialize
80
95
  # Default feature toggles - all enabled
81
96
  @enabled = true
@@ -84,10 +99,17 @@ module RailsConsolePro
84
99
  @navigate_command_enabled = true
85
100
  @stats_command_enabled = true
86
101
  @diff_command_enabled = true
102
+ @snippets_command_enabled = true
103
+ @profile_command_enabled = true
104
+ @queue_command_enabled = true
105
+ @introspect_command_enabled = true
106
+ @compare_command_enabled = true
107
+ @query_builder_command_enabled = true
87
108
  @active_record_printer_enabled = true
88
109
  @relation_printer_enabled = true
89
110
  @collection_printer_enabled = true
90
111
  @export_enabled = true
112
+ @snippet_store_path = default_snippet_store_path
91
113
 
92
114
  # Default color scheme
93
115
  @color_scheme = :dark
@@ -138,6 +160,11 @@ module RailsConsolePro
138
160
  # Default stats calculation settings
139
161
  @stats_large_table_threshold = 10_000 # Consider table large if it has 10k+ records
140
162
  @stats_skip_distinct_threshold = 10_000 # Skip distinct count for tables with 10k+ records
163
+
164
+ # Default profiling settings
165
+ @profile_slow_query_threshold = 100.0 # milliseconds
166
+ @profile_duplicate_query_threshold = 2
167
+ @profile_max_saved_queries = 10
141
168
  end
142
169
 
143
170
  # Set color scheme (dark, light, or custom)
@@ -190,6 +217,10 @@ module RailsConsolePro
190
217
  @navigate_command_enabled = false
191
218
  @stats_command_enabled = false
192
219
  @diff_command_enabled = false
220
+ @queue_command_enabled = false
221
+ @introspect_command_enabled = false
222
+ @compare_command_enabled = false
223
+ @query_builder_command_enabled = false
193
224
  @active_record_printer_enabled = false
194
225
  @relation_printer_enabled = false
195
226
  @collection_printer_enabled = false
@@ -204,6 +235,10 @@ module RailsConsolePro
204
235
  @navigate_command_enabled = true
205
236
  @stats_command_enabled = true
206
237
  @diff_command_enabled = true
238
+ @queue_command_enabled = true
239
+ @introspect_command_enabled = true
240
+ @compare_command_enabled = true
241
+ @query_builder_command_enabled = true
207
242
  @active_record_printer_enabled = true
208
243
  @relation_printer_enabled = true
209
244
  @collection_printer_enabled = true
@@ -214,6 +249,22 @@ module RailsConsolePro
214
249
  def reset
215
250
  initialize
216
251
  end
252
+
253
+ private
254
+
255
+ def default_snippet_store_path
256
+ base_path =
257
+ if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
258
+ Rails.root.join('tmp', 'rails_console_pro')
259
+ else
260
+ File.expand_path(File.join(Dir.respond_to?(:pwd) ? Dir.pwd : Dir.tmpdir, 'tmp', 'rails_console_pro'))
261
+ end
262
+
263
+ base_path = Pathname.new(base_path) unless base_path.is_a?(Pathname)
264
+ (base_path + 'snippets.yml').to_s
265
+ rescue StandardError
266
+ File.join(Dir.tmpdir, 'rails_console_pro', 'snippets.yml')
267
+ end
217
268
  end
218
269
  end
219
270
 
@@ -81,6 +81,14 @@ module RailsConsolePro
81
81
  serialize_diff_result(data)
82
82
  when ExplainResult
83
83
  serialize_explain_result(data)
84
+ when ProfileResult
85
+ serialize_profile_result(data)
86
+ when IntrospectResult
87
+ serialize_introspect_result(data)
88
+ when CompareResult
89
+ serialize_compare_result(data)
90
+ when QueryBuilderResult
91
+ serialize_query_builder_result(data)
84
92
  when ActiveRecord::Base
85
93
  serialize_active_record(data)
86
94
  when ActiveRecord::Relation
@@ -131,6 +139,22 @@ module RailsConsolePro
131
139
  Serializers::DiffSerializer.serialize(result, self)
132
140
  end
133
141
 
142
+ def serialize_profile_result(result)
143
+ Serializers::ProfileSerializer.serialize(result, self)
144
+ end
145
+
146
+ def serialize_introspect_result(result)
147
+ Serializers::IntrospectSerializer.serialize(result, self)
148
+ end
149
+
150
+ def serialize_compare_result(result)
151
+ Serializers::CompareSerializer.serialize(result, self)
152
+ end
153
+
154
+ def serialize_query_builder_result(result)
155
+ Serializers::QueryBuilderSerializer.serialize(result, self)
156
+ end
157
+
134
158
  def serialize_active_record(record)
135
159
  Serializers::ActiveRecordSerializer.serialize(record, self)
136
160
  end
@@ -388,6 +412,14 @@ module RailsConsolePro
388
412
  "Diff Comparison: #{data.object1_type} vs #{data.object2_type}"
389
413
  when ExplainResult
390
414
  "SQL Explain Analysis"
415
+ when ProfileResult
416
+ "Profile: #{data.label || 'Session'}"
417
+ when IntrospectResult
418
+ "Introspection: #{data.model.name}"
419
+ when CompareResult
420
+ "Query Comparison (#{data.comparisons.size} strategies)"
421
+ when QueryBuilderResult
422
+ "Query Builder: #{data.model_class.name}"
391
423
  when ActiveRecord::Base
392
424
  "#{data.class.name} ##{data.id}"
393
425
  when ActiveRecord::Relation
@@ -40,3 +40,27 @@ def diff(object1, object2)
40
40
  RailsConsolePro::Commands.diff(object1, object2)
41
41
  end
42
42
 
43
+ def profile(target = nil, *args, **kwargs, &block)
44
+ RailsConsolePro::Commands.profile(target, *args, **kwargs, &block)
45
+ end
46
+
47
+ def jobs(options = {})
48
+ RailsConsolePro::Commands.jobs(options)
49
+ end
50
+
51
+ def snippets(action = :list, *args, **kwargs, &block)
52
+ RailsConsolePro::Commands.snippets(action, *args, **kwargs, &block)
53
+ end
54
+
55
+ def introspect(model_class, *options)
56
+ RailsConsolePro::Commands.introspect(model_class, *options)
57
+ end
58
+
59
+ def compare(&block)
60
+ RailsConsolePro::Commands.compare(&block)
61
+ end
62
+
63
+ def query(model_class, &block)
64
+ RailsConsolePro::Commands.query(model_class, &block)
65
+ end
66
+
@@ -31,11 +31,18 @@ module RailsConsolePro
31
31
  autoload :ExplainResult, "rails_console_pro/explain_result"
32
32
  autoload :StatsResult, "rails_console_pro/stats_result"
33
33
  autoload :DiffResult, "rails_console_pro/diff_result"
34
+ autoload :ProfileResult, "rails_console_pro/profile_result"
35
+ autoload :QueueInsightsResult, "rails_console_pro/queue_insights_result"
36
+ autoload :IntrospectResult, "rails_console_pro/introspect_result"
37
+ autoload :CompareResult, "rails_console_pro/compare_result"
38
+ autoload :QueryBuilderResult, "rails_console_pro/query_builder_result"
39
+ autoload :QueryBuilder, "rails_console_pro/query_builder"
34
40
  autoload :AssociationNavigator, "rails_console_pro/association_navigator"
35
41
  autoload :Commands, "rails_console_pro/commands"
36
42
  autoload :FormatExporter, "rails_console_pro/format_exporter"
37
43
  autoload :ErrorHandler, "rails_console_pro/error_handler"
38
44
  autoload :Paginator, "rails_console_pro/paginator"
45
+ autoload :Snippets, "rails_console_pro/snippets"
39
46
 
40
47
  module Printers
41
48
  autoload :ActiveRecordPrinter, "rails_console_pro/printers/active_record_printer"
@@ -45,6 +52,13 @@ module RailsConsolePro
45
52
  autoload :ExplainPrinter, "rails_console_pro/printers/explain_printer"
46
53
  autoload :StatsPrinter, "rails_console_pro/printers/stats_printer"
47
54
  autoload :DiffPrinter, "rails_console_pro/printers/diff_printer"
55
+ autoload :ProfilePrinter, "rails_console_pro/printers/profile_printer"
56
+ autoload :IntrospectPrinter, "rails_console_pro/printers/introspect_printer"
57
+ autoload :SnippetCollectionPrinter, "rails_console_pro/printers/snippet_collection_printer"
58
+ autoload :SnippetPrinter, "rails_console_pro/printers/snippet_printer"
59
+ autoload :QueueInsightsPrinter, "rails_console_pro/printers/queue_insights_printer"
60
+ autoload :ComparePrinter, "rails_console_pro/printers/compare_printer"
61
+ autoload :QueryBuilderPrinter, "rails_console_pro/printers/query_builder_printer"
48
62
  end
49
63
 
50
64
  # Main dispatcher - optimized with early returns
@@ -88,6 +102,17 @@ module RailsConsolePro
88
102
  return Printers::ExplainPrinter if value.is_a?(ExplainResult)
89
103
  return Printers::StatsPrinter if value.is_a?(StatsResult)
90
104
  return Printers::DiffPrinter if value.is_a?(DiffResult)
105
+ return Printers::ProfilePrinter if value.is_a?(ProfileResult)
106
+ return Printers::IntrospectPrinter if value.is_a?(IntrospectResult)
107
+ return Printers::QueueInsightsPrinter if value.is_a?(QueueInsightsResult)
108
+ return Printers::ComparePrinter if value.is_a?(CompareResult)
109
+ return Printers::QueryBuilderPrinter if value.is_a?(QueryBuilderResult)
110
+ if defined?(Snippets::CollectionResult) && value.is_a?(Snippets::CollectionResult)
111
+ return Printers::SnippetCollectionPrinter
112
+ end
113
+ if defined?(Snippets::SingleResult) && value.is_a?(Snippets::SingleResult)
114
+ return Printers::SnippetPrinter
115
+ end
91
116
 
92
117
  # Fallback to base printer
93
118
  BasePrinter
@@ -139,6 +164,11 @@ require_relative 'services/stats_calculator'
139
164
  require_relative 'services/table_size_calculator'
140
165
  require_relative 'services/index_analyzer'
141
166
  require_relative 'services/column_stats_calculator'
167
+ require_relative 'services/profile_collector'
168
+ require_relative 'services/snippet_repository'
169
+ require_relative 'services/queue_action_service'
170
+ require_relative 'services/queue_insight_fetcher'
171
+ require_relative 'services/introspection_collector'
142
172
 
143
173
  # Load command classes (needed by Commands module)
144
174
  require_relative 'commands/base_command'
@@ -147,6 +177,12 @@ require_relative 'commands/explain_command'
147
177
  require_relative 'commands/stats_command'
148
178
  require_relative 'commands/diff_command'
149
179
  require_relative 'commands/export_command'
180
+ require_relative 'commands/introspect_command'
181
+ require_relative 'commands/snippets_command'
182
+ require_relative 'commands/profile_command'
183
+ require_relative 'commands/jobs_command'
184
+ require_relative 'commands/compare_command'
185
+ require_relative 'commands/query_builder_command'
150
186
 
151
187
  # Load Commands module (uses command classes)
152
188
  require_relative 'commands'
@@ -157,6 +193,10 @@ require_relative 'serializers/schema_serializer'
157
193
  require_relative 'serializers/stats_serializer'
158
194
  require_relative 'serializers/explain_serializer'
159
195
  require_relative 'serializers/diff_serializer'
196
+ require_relative 'serializers/profile_serializer'
197
+ require_relative 'serializers/introspect_serializer'
198
+ require_relative 'serializers/compare_serializer'
199
+ require_relative 'serializers/query_builder_serializer'
160
200
  require_relative 'serializers/active_record_serializer'
161
201
  require_relative 'serializers/relation_serializer'
162
202
  require_relative 'serializers/array_serializer'
@@ -173,6 +213,6 @@ end
173
213
  if RailsConsolePro.config.show_welcome_message && defined?(Pry)
174
214
  pastel = RailsConsolePro::PASTEL
175
215
  puts pastel.bright_green("🚀 Rails Console Pro Loaded!")
176
- puts pastel.cyan("📊 Use `schema ModelName`, `explain Query`, `stats ModelName`, `diff obj1, obj2`, or `navigate ModelName`")
216
+ puts pastel.cyan("📊 Use `schema`, `explain`, `stats`, `introspect`, `diff`, `navigate`, `profile`, or `jobs` commands")
177
217
  puts pastel.dim("💾 Export support: Use `.to_json`, `.to_yaml`, `.to_html`, or `.export_to_file` on any result")
178
218
  end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsConsolePro
4
+ # Value object for model introspection results
5
+ class IntrospectResult
6
+ attr_reader :model, :callbacks, :enums, :concerns, :scopes,
7
+ :validations, :lifecycle_hooks, :timestamp
8
+
9
+ def initialize(model:, callbacks: {}, enums: {}, concerns: [],
10
+ scopes: {}, validations: [], lifecycle_hooks: {},
11
+ timestamp: Time.current)
12
+ @model = model
13
+ @callbacks = callbacks
14
+ @enums = enums
15
+ @concerns = concerns
16
+ @scopes = scopes
17
+ @validations = validations
18
+ @lifecycle_hooks = lifecycle_hooks
19
+ @timestamp = timestamp
20
+ validate_model!
21
+ end
22
+
23
+ def ==(other)
24
+ other.is_a?(self.class) && other.model == model && other.timestamp == timestamp
25
+ end
26
+
27
+ # Query methods
28
+ def has_callbacks?
29
+ callbacks.any?
30
+ end
31
+
32
+ def has_enums?
33
+ enums.any?
34
+ end
35
+
36
+ def has_concerns?
37
+ concerns.any?
38
+ end
39
+
40
+ def has_scopes?
41
+ scopes.any?
42
+ end
43
+
44
+ def has_validations?
45
+ validations.any?
46
+ end
47
+
48
+ # Get callbacks by type
49
+ def callbacks_by_type(type)
50
+ callbacks[type] || []
51
+ end
52
+
53
+ # Get validations for attribute
54
+ def validations_for(attribute)
55
+ validations[attribute] || []
56
+ end
57
+
58
+ # Get enum values
59
+ def enum_values(enum_name)
60
+ enums.dig(enum_name.to_s, :values) || []
61
+ end
62
+
63
+ # Get scope SQL
64
+ def scope_sql(scope_name)
65
+ scopes.dig(scope_name.to_sym, :sql)
66
+ end
67
+
68
+ # Get method source location
69
+ def method_source(method_name)
70
+ collector = Services::IntrospectionCollector.new(model)
71
+ collector.method_source_location(method_name)
72
+ end
73
+
74
+ # Export to JSON
75
+ def to_json(pretty: true)
76
+ FormatExporter.to_json(self, pretty: pretty)
77
+ end
78
+
79
+ # Export to YAML
80
+ def to_yaml
81
+ FormatExporter.to_yaml(self)
82
+ end
83
+
84
+ # Export to HTML
85
+ def to_html(style: :default)
86
+ FormatExporter.to_html(self, title: "Introspection: #{model.name}", style: style)
87
+ end
88
+
89
+ # Export to file
90
+ def export_to_file(file_path, format: nil)
91
+ FormatExporter.export_to_file(self, file_path, format: format)
92
+ end
93
+
94
+ private
95
+
96
+ def validate_model!
97
+ ModelValidator.validate_model!(model)
98
+ end
99
+ end
100
+ end
101
+
@@ -27,7 +27,7 @@ module RailsConsolePro
27
27
  # Check if model is abstract
28
28
  def abstract_class?(model_class)
29
29
  return false unless valid_model?(model_class)
30
- model_class.abstract_class?
30
+ !!model_class.abstract_class?
31
31
  end
32
32
 
33
33
  # Check if model uses Single Table Inheritance (STI)
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsConsolePro
4
+ module Printers
5
+ # Printer for query comparison results
6
+ class ComparePrinter < BasePrinter
7
+ def print
8
+ print_header
9
+ print_summary
10
+ print_comparisons
11
+ print_winner
12
+ print_footer
13
+ end
14
+
15
+ private
16
+
17
+ def print_header
18
+ header_color = config.get_color(:header)
19
+ output.puts bold_color(header_color, "═" * config.header_width)
20
+ output.puts bold_color(header_color, "⚖️ QUERY COMPARISON")
21
+ output.puts bold_color(header_color, "═" * config.header_width)
22
+ end
23
+
24
+ def print_summary
25
+ output.puts bold_color(config.get_color(:warning), "\n📊 Summary:")
26
+ output.puts " Total Strategies: #{color(config.get_color(:attribute_value_numeric), value.comparisons.size)}"
27
+ output.puts " Fastest: #{color(config.get_color(:success), value.fastest_name || 'N/A')}"
28
+ output.puts " Slowest: #{color(config.get_color(:error), value.slowest_name || 'N/A')}"
29
+
30
+ if value.performance_ratio
31
+ ratio = value.performance_ratio
32
+ ratio_color = ratio > 2 ? config.get_color(:error) : config.get_color(:warning)
33
+ output.puts " Performance Ratio: #{color(ratio_color, "#{ratio}x slower")}"
34
+ end
35
+
36
+ if value.has_errors?
37
+ output.puts " Errors: #{color(config.get_color(:error), value.error_count.to_s)}"
38
+ end
39
+ end
40
+
41
+ def print_comparisons
42
+ output.puts bold_color(config.get_color(:warning), "\n📈 Detailed Results:")
43
+
44
+ sorted = value.comparisons.sort_by { |c| c.duration_ms || Float::INFINITY }
45
+
46
+ sorted.each_with_index do |comparison, index|
47
+ print_comparison(comparison, index + 1, sorted.size)
48
+ end
49
+ end
50
+
51
+ def print_comparison(comparison, rank, total)
52
+ is_winner = comparison == value.fastest
53
+ rank_color = is_winner ? config.get_color(:success) : config.get_color(:attribute_value_string)
54
+
55
+ output.puts "\n #{color(rank_color, "##{rank}")} #{bold_color(rank_color, comparison.name)}"
56
+
57
+ if comparison.error
58
+ output.puts " #{color(config.get_color(:error), "❌ Error: #{comparison.error.class} - #{comparison.error.message}")}"
59
+ return
60
+ end
61
+
62
+ # Duration
63
+ duration_str = format_duration(comparison.duration_ms)
64
+ output.puts " ⏱️ Duration: #{duration_str}"
65
+
66
+ # Query count
67
+ query_color = comparison.query_count > 10 ? config.get_color(:error) :
68
+ comparison.query_count > 1 ? config.get_color(:warning) :
69
+ config.get_color(:success)
70
+ output.puts " 🔢 Queries: #{color(query_color, comparison.query_count.to_s)}"
71
+
72
+ # Memory usage
73
+ if comparison.memory_usage_kb && comparison.memory_usage_kb > 0
74
+ memory_str = format_memory(comparison.memory_usage_kb)
75
+ output.puts " 💾 Memory: #{color(config.get_color(:info), memory_str)}"
76
+ end
77
+
78
+ # SQL queries preview
79
+ if comparison.sql_queries&.any?
80
+ preview_count = [comparison.sql_queries.size, 3].min
81
+ output.puts " 📝 SQL Queries (#{comparison.sql_queries.size} total):"
82
+ comparison.sql_queries.first(preview_count).each_with_index do |query_info, idx|
83
+ sql_preview = truncate_sql(query_info[:sql], 60)
84
+ duration = query_info[:duration_ms]
85
+ cached = query_info[:cached] ? " (cached)" : ""
86
+ output.puts " #{idx + 1}. #{color(config.get_color(:attribute_value_string), sql_preview)} #{color(config.get_color(:border), "(#{duration}ms#{cached})")}"
87
+ end
88
+ if comparison.sql_queries.size > preview_count
89
+ output.puts " ... and #{comparison.sql_queries.size - preview_count} more"
90
+ end
91
+ end
92
+ end
93
+
94
+ def print_winner
95
+ return unless value.winner
96
+
97
+ output.puts bold_color(config.get_color(:success), "\n🏆 Winner: #{value.winner.name}")
98
+ if value.performance_ratio && value.performance_ratio > 1
99
+ output.puts " #{color(config.get_color(:info), "This strategy is #{value.performance_ratio.round(1)}x faster than the slowest")}"
100
+ end
101
+ end
102
+
103
+ def print_footer
104
+ footer_color = config.get_color(:footer)
105
+ output.puts bold_color(footer_color, "═" * config.header_width)
106
+ end
107
+
108
+ def format_duration(ms)
109
+ return color(config.get_color(:attribute_value_nil), "N/A") unless ms
110
+
111
+ case ms
112
+ when 0...10
113
+ color(config.get_color(:success), "#{ms.round(2)}ms")
114
+ when 10...100
115
+ color(config.get_color(:warning), "#{ms.round(2)}ms")
116
+ else
117
+ color(config.get_color(:error), "#{ms.round(2)}ms")
118
+ end
119
+ end
120
+
121
+ def format_memory(kb)
122
+ if kb < 1024
123
+ "#{kb.round(2)} KB"
124
+ elsif kb < 1024 * 1024
125
+ "#{(kb / 1024.0).round(2)} MB"
126
+ else
127
+ "#{(kb / (1024.0 * 1024.0)).round(2)} GB"
128
+ end
129
+ end
130
+
131
+ def truncate_sql(sql, max_length)
132
+ return sql if sql.length <= max_length
133
+ "#{sql[0, max_length - 3]}..."
134
+ end
135
+ end
136
+ end
137
+ end
138
+