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.
- checksums.yaml +4 -4
- data/.rspec_status +288 -240
- data/CHANGELOG.md +7 -0
- data/QUICK_START.md +17 -0
- data/README.md +43 -0
- data/docs/FORMATTING.md +5 -0
- data/docs/MODEL_INTROSPECTION.md +371 -0
- data/docs/MODEL_STATISTICS.md +4 -0
- data/docs/OBJECT_DIFFING.md +6 -0
- data/docs/PROFILING.md +91 -0
- data/docs/QUERY_BUILDER.md +385 -0
- data/docs/QUEUE_INSIGHTS.md +82 -0
- data/docs/SCHEMA_INSPECTION.md +5 -0
- data/docs/SNIPPETS.md +71 -0
- data/lib/rails_console_pro/commands/compare_command.rb +151 -0
- data/lib/rails_console_pro/commands/introspect_command.rb +220 -0
- data/lib/rails_console_pro/commands/jobs_command.rb +212 -0
- data/lib/rails_console_pro/commands/profile_command.rb +84 -0
- data/lib/rails_console_pro/commands/query_builder_command.rb +43 -0
- data/lib/rails_console_pro/commands/snippets_command.rb +141 -0
- data/lib/rails_console_pro/commands.rb +30 -0
- data/lib/rails_console_pro/compare_result.rb +81 -0
- data/lib/rails_console_pro/configuration.rb +51 -0
- data/lib/rails_console_pro/format_exporter.rb +32 -0
- data/lib/rails_console_pro/global_methods.rb +24 -0
- data/lib/rails_console_pro/initializer.rb +41 -1
- data/lib/rails_console_pro/introspect_result.rb +101 -0
- data/lib/rails_console_pro/model_validator.rb +1 -1
- data/lib/rails_console_pro/printers/compare_printer.rb +138 -0
- data/lib/rails_console_pro/printers/introspect_printer.rb +282 -0
- data/lib/rails_console_pro/printers/profile_printer.rb +180 -0
- data/lib/rails_console_pro/printers/query_builder_printer.rb +81 -0
- data/lib/rails_console_pro/printers/queue_insights_printer.rb +150 -0
- data/lib/rails_console_pro/printers/snippet_collection_printer.rb +68 -0
- data/lib/rails_console_pro/printers/snippet_printer.rb +64 -0
- data/lib/rails_console_pro/profile_result.rb +109 -0
- data/lib/rails_console_pro/pry_commands.rb +106 -0
- data/lib/rails_console_pro/query_builder.rb +197 -0
- data/lib/rails_console_pro/query_builder_result.rb +66 -0
- data/lib/rails_console_pro/queue_insights_result.rb +110 -0
- data/lib/rails_console_pro/serializers/compare_serializer.rb +66 -0
- data/lib/rails_console_pro/serializers/introspect_serializer.rb +99 -0
- data/lib/rails_console_pro/serializers/profile_serializer.rb +73 -0
- data/lib/rails_console_pro/serializers/query_builder_serializer.rb +35 -0
- data/lib/rails_console_pro/services/introspection_collector.rb +420 -0
- data/lib/rails_console_pro/services/profile_collector.rb +245 -0
- data/lib/rails_console_pro/services/queue_action_service.rb +176 -0
- data/lib/rails_console_pro/services/queue_insight_fetcher.rb +600 -0
- data/lib/rails_console_pro/services/snippet_repository.rb +191 -0
- data/lib/rails_console_pro/snippets/collection_result.rb +45 -0
- data/lib/rails_console_pro/snippets/single_result.rb +30 -0
- data/lib/rails_console_pro/snippets/snippet.rb +112 -0
- data/lib/rails_console_pro/snippets.rb +13 -0
- data/lib/rails_console_pro/version.rb +1 -1
- data/rails_console_pro.gemspec +1 -1
- 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
|
|
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
|
+
|