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.
- checksums.yaml +7 -0
- data/.editorconfig +12 -0
- data/.rspec +4 -0
- data/.rspec_status +240 -0
- data/.rubocop.yml +26 -0
- data/CHANGELOG.md +24 -0
- data/CONTRIBUTING.md +76 -0
- data/LICENSE.txt +22 -0
- data/QUICK_START.md +112 -0
- data/README.md +124 -0
- data/Rakefile +13 -0
- data/config/database.yml +3 -0
- data/docs/ASSOCIATION_NAVIGATION.md +85 -0
- data/docs/EXPORT.md +95 -0
- data/docs/FORMATTING.md +86 -0
- data/docs/MODEL_STATISTICS.md +72 -0
- data/docs/OBJECT_DIFFING.md +87 -0
- data/docs/SCHEMA_INSPECTION.md +60 -0
- data/docs/SQL_EXPLAIN.md +70 -0
- data/lib/generators/rails_console_pro/install_generator.rb +16 -0
- data/lib/generators/rails_console_pro/templates/rails_console_pro.rb +44 -0
- data/lib/rails_console_pro/active_record_extensions.rb +113 -0
- data/lib/rails_console_pro/association_navigator.rb +273 -0
- data/lib/rails_console_pro/base_printer.rb +74 -0
- data/lib/rails_console_pro/color_helper.rb +36 -0
- data/lib/rails_console_pro/commands/base_command.rb +17 -0
- data/lib/rails_console_pro/commands/diff_command.rb +135 -0
- data/lib/rails_console_pro/commands/explain_command.rb +118 -0
- data/lib/rails_console_pro/commands/export_command.rb +16 -0
- data/lib/rails_console_pro/commands/schema_command.rb +20 -0
- data/lib/rails_console_pro/commands/stats_command.rb +93 -0
- data/lib/rails_console_pro/commands.rb +34 -0
- data/lib/rails_console_pro/configuration.rb +219 -0
- data/lib/rails_console_pro/diff_result.rb +56 -0
- data/lib/rails_console_pro/error_handler.rb +60 -0
- data/lib/rails_console_pro/explain_result.rb +47 -0
- data/lib/rails_console_pro/format_exporter.rb +403 -0
- data/lib/rails_console_pro/global_methods.rb +42 -0
- data/lib/rails_console_pro/initializer.rb +176 -0
- data/lib/rails_console_pro/model_validator.rb +219 -0
- data/lib/rails_console_pro/paginator.rb +204 -0
- data/lib/rails_console_pro/printers/active_record_printer.rb +30 -0
- data/lib/rails_console_pro/printers/collection_printer.rb +34 -0
- data/lib/rails_console_pro/printers/diff_printer.rb +97 -0
- data/lib/rails_console_pro/printers/explain_printer.rb +151 -0
- data/lib/rails_console_pro/printers/relation_printer.rb +25 -0
- data/lib/rails_console_pro/printers/schema_printer.rb +188 -0
- data/lib/rails_console_pro/printers/stats_printer.rb +129 -0
- data/lib/rails_console_pro/pry_commands.rb +241 -0
- data/lib/rails_console_pro/pry_integration.rb +9 -0
- data/lib/rails_console_pro/railtie.rb +29 -0
- data/lib/rails_console_pro/schema_inspector_result.rb +43 -0
- data/lib/rails_console_pro/serializers/active_record_serializer.rb +18 -0
- data/lib/rails_console_pro/serializers/array_serializer.rb +31 -0
- data/lib/rails_console_pro/serializers/base_serializer.rb +25 -0
- data/lib/rails_console_pro/serializers/diff_serializer.rb +24 -0
- data/lib/rails_console_pro/serializers/explain_serializer.rb +35 -0
- data/lib/rails_console_pro/serializers/relation_serializer.rb +25 -0
- data/lib/rails_console_pro/serializers/schema_serializer.rb +121 -0
- data/lib/rails_console_pro/serializers/stats_serializer.rb +24 -0
- data/lib/rails_console_pro/services/column_stats_calculator.rb +64 -0
- data/lib/rails_console_pro/services/index_analyzer.rb +110 -0
- data/lib/rails_console_pro/services/stats_calculator.rb +40 -0
- data/lib/rails_console_pro/services/table_size_calculator.rb +43 -0
- data/lib/rails_console_pro/stats_result.rb +66 -0
- data/lib/rails_console_pro/version.rb +6 -0
- data/lib/rails_console_pro.rb +14 -0
- data/lib/tasks/rails_console_pro.rake +10 -0
- data/rails_console_pro.gemspec +60 -0
- metadata +240 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Pry command definitions
|
|
4
|
+
if defined?(Pry)
|
|
5
|
+
Pry::Commands.create_command "schema" do
|
|
6
|
+
description "Inspect database schema for a model"
|
|
7
|
+
|
|
8
|
+
def process
|
|
9
|
+
pastel = RailsConsolePro::ColorHelper.pastel
|
|
10
|
+
unless RailsConsolePro.config.schema_command_enabled
|
|
11
|
+
output.puts pastel.yellow("Schema command is disabled. Enable it with: RailsConsolePro.configure { |c| c.schema_command_enabled = true }")
|
|
12
|
+
return
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
model_name = args.first
|
|
16
|
+
if model_name.nil?
|
|
17
|
+
output.puts pastel.red("Usage: schema ModelName")
|
|
18
|
+
output.puts pastel.yellow("Example: schema User")
|
|
19
|
+
return
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
begin
|
|
23
|
+
model_class = model_name.constantize
|
|
24
|
+
result = RailsConsolePro::Commands.schema(model_class)
|
|
25
|
+
RailsConsolePro.call(output, result, pry_instance) if result
|
|
26
|
+
rescue NameError => e
|
|
27
|
+
output.puts pastel.red("Error: Could not find model '#{model_name}'")
|
|
28
|
+
output.puts pastel.yellow("Make sure the model name is correct and loaded.")
|
|
29
|
+
rescue => e
|
|
30
|
+
output.puts pastel.red("Error: #{e.message}")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
Pry::Commands.create_command "explain" do
|
|
36
|
+
description "Analyze SQL query execution plan"
|
|
37
|
+
|
|
38
|
+
def process
|
|
39
|
+
pastel = RailsConsolePro::ColorHelper.pastel
|
|
40
|
+
unless RailsConsolePro.config.explain_command_enabled
|
|
41
|
+
output.puts pastel.yellow("Explain command is disabled. Enable it with: RailsConsolePro.configure { |c| c.explain_command_enabled = true }")
|
|
42
|
+
return
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if args.empty?
|
|
46
|
+
output.puts pastel.red("Usage: explain Model.where(...) or explain(Model, conditions)")
|
|
47
|
+
return
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
begin
|
|
51
|
+
relation = eval(args.join(' '), target)
|
|
52
|
+
result = RailsConsolePro::Commands.explain(relation)
|
|
53
|
+
output.puts result if result
|
|
54
|
+
rescue => e
|
|
55
|
+
output.puts pastel.red("Error: #{e.message}")
|
|
56
|
+
output.puts pastel.yellow("💡 Use: explain(Model.where(...)) or explain(Model, key: value)")
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
Pry::Commands.create_command "navigate" do
|
|
62
|
+
description "Navigate through model associations interactively"
|
|
63
|
+
|
|
64
|
+
def process
|
|
65
|
+
pastel = RailsConsolePro::ColorHelper.pastel
|
|
66
|
+
unless RailsConsolePro.config.navigate_command_enabled
|
|
67
|
+
output.puts pastel.yellow("Navigate command is disabled. Enable it with: RailsConsolePro.configure { |c| c.navigate_command_enabled = true }")
|
|
68
|
+
return
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
model_name = args.first
|
|
72
|
+
if model_name.nil?
|
|
73
|
+
output.puts pastel.red("Usage: navigate ModelName")
|
|
74
|
+
output.puts pastel.yellow("Example: navigate User")
|
|
75
|
+
return
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
begin
|
|
79
|
+
model = model_name.constantize
|
|
80
|
+
navigator = RailsConsolePro::AssociationNavigator.new(model)
|
|
81
|
+
navigator.start
|
|
82
|
+
rescue ArgumentError => e
|
|
83
|
+
output.puts pastel.red("Error: #{e.message}")
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
Pry::Commands.create_command "export" do
|
|
89
|
+
description "Export data to JSON, YAML, or HTML file"
|
|
90
|
+
|
|
91
|
+
def process
|
|
92
|
+
pastel = RailsConsolePro::ColorHelper.pastel
|
|
93
|
+
unless RailsConsolePro.config.export_enabled
|
|
94
|
+
output.puts pastel.yellow("Export command is disabled. Enable it with: RailsConsolePro.configure { |c| c.export_enabled = true }")
|
|
95
|
+
return
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if args.empty?
|
|
99
|
+
show_usage
|
|
100
|
+
return
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
begin
|
|
104
|
+
if args.size < 2
|
|
105
|
+
show_usage
|
|
106
|
+
return
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Simple parsing: export <data> <file_path> [format]
|
|
110
|
+
file_path = args[-1].gsub(/^['"]|['"]$/, '')
|
|
111
|
+
data_expr = args[0..-2].join(' ')
|
|
112
|
+
|
|
113
|
+
format_keywords = %w[json yaml yml html htm]
|
|
114
|
+
if args.size >= 3 && format_keywords.include?(args[-2].downcase)
|
|
115
|
+
format = args[-2].downcase
|
|
116
|
+
data_expr = args[0..-3].join(' ')
|
|
117
|
+
else
|
|
118
|
+
format = nil
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
data = eval(data_expr, target)
|
|
122
|
+
result = RailsConsolePro::Commands.export(data, file_path, format: format)
|
|
123
|
+
|
|
124
|
+
if result
|
|
125
|
+
output.puts pastel.green("✅ Exported to: #{File.expand_path(result)}")
|
|
126
|
+
else
|
|
127
|
+
output.puts pastel.red("❌ Export failed")
|
|
128
|
+
end
|
|
129
|
+
rescue SyntaxError => e
|
|
130
|
+
output.puts pastel.red("Syntax Error: #{e.message}")
|
|
131
|
+
show_usage
|
|
132
|
+
rescue => e
|
|
133
|
+
output.puts pastel.red("Error: #{e.message}")
|
|
134
|
+
output.puts pastel.yellow("💡 Tip: Make sure the data is exportable (schema result, explain result, ActiveRecord object, etc.)")
|
|
135
|
+
show_usage
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
def show_usage
|
|
142
|
+
pastel = RailsConsolePro::ColorHelper.pastel
|
|
143
|
+
output.puts pastel.red("Usage: export <data> <file_path> [format]")
|
|
144
|
+
output.puts pastel.yellow("")
|
|
145
|
+
output.puts pastel.yellow("Examples:")
|
|
146
|
+
output.puts pastel.cyan(" export schema(User) user_schema.json")
|
|
147
|
+
output.puts pastel.cyan(" export User.first user.html html")
|
|
148
|
+
output.puts pastel.cyan(" export explain(User.where(active: true)) query.json")
|
|
149
|
+
output.puts pastel.cyan(" export User.all users.json")
|
|
150
|
+
output.puts pastel.yellow("")
|
|
151
|
+
output.puts pastel.yellow("Formats: json, yaml, html (auto-detected from file extension if not specified)")
|
|
152
|
+
output.puts pastel.yellow("")
|
|
153
|
+
output.puts pastel.dim("💡 Tip: You can also use methods directly:")
|
|
154
|
+
output.puts pastel.dim(" schema(User).export_to_file('user.json')")
|
|
155
|
+
output.puts pastel.dim(" User.first.export_to_file('user.html', format: 'html')")
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
Pry::Commands.create_command "stats" do
|
|
160
|
+
description "Show model statistics (record count, growth rate, table size, index usage)"
|
|
161
|
+
|
|
162
|
+
def process
|
|
163
|
+
pastel = RailsConsolePro::ColorHelper.pastel
|
|
164
|
+
unless RailsConsolePro.config.stats_command_enabled
|
|
165
|
+
output.puts pastel.yellow("Stats command is disabled. Enable it with: RailsConsolePro.configure { |c| c.stats_command_enabled = true }")
|
|
166
|
+
return
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
model_name = args.first
|
|
170
|
+
if model_name.nil?
|
|
171
|
+
output.puts pastel.red("Usage: stats ModelName")
|
|
172
|
+
output.puts pastel.yellow("Example: stats User")
|
|
173
|
+
return
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
begin
|
|
177
|
+
model_class = model_name.constantize
|
|
178
|
+
result = RailsConsolePro::Commands.stats(model_class)
|
|
179
|
+
RailsConsolePro.call(output, result, pry_instance) if result
|
|
180
|
+
rescue NameError => e
|
|
181
|
+
output.puts pastel.red("Error: Could not find model '#{model_name}'")
|
|
182
|
+
output.puts pastel.yellow("Make sure the model name is correct and loaded.")
|
|
183
|
+
rescue => e
|
|
184
|
+
output.puts pastel.red("Error: #{e.message}")
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
Pry::Commands.create_command "diff" do
|
|
190
|
+
description "Compare two objects and highlight differences"
|
|
191
|
+
|
|
192
|
+
def process
|
|
193
|
+
pastel = RailsConsolePro::ColorHelper.pastel
|
|
194
|
+
unless RailsConsolePro.config.diff_command_enabled
|
|
195
|
+
output.puts pastel.yellow("Diff command is disabled. Enable it with: RailsConsolePro.configure { |c| c.diff_command_enabled = true }")
|
|
196
|
+
return
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
if args.empty?
|
|
200
|
+
output.puts pastel.red("Usage: diff object1, object2")
|
|
201
|
+
output.puts pastel.yellow("Examples:")
|
|
202
|
+
output.puts pastel.cyan(" diff User.first, User.last")
|
|
203
|
+
output.puts pastel.cyan(" diff user1, user2")
|
|
204
|
+
output.puts pastel.cyan(" diff {a: 1}, {a: 2}")
|
|
205
|
+
return
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
begin
|
|
209
|
+
all_args = args.join(' ')
|
|
210
|
+
parts = all_args.split(',').map(&:strip)
|
|
211
|
+
|
|
212
|
+
if parts.size < 2
|
|
213
|
+
output.puts pastel.red("Error: Need two objects to compare (separated by comma)")
|
|
214
|
+
output.puts pastel.yellow("Usage: diff object1, object2")
|
|
215
|
+
return
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
object1 = eval(parts[0], target)
|
|
219
|
+
object2 = eval(parts[1], target)
|
|
220
|
+
|
|
221
|
+
result = RailsConsolePro::Commands.diff(object1, object2)
|
|
222
|
+
RailsConsolePro.call(output, result, pry_instance) if result
|
|
223
|
+
rescue SyntaxError => e
|
|
224
|
+
output.puts pastel.red("Syntax Error: #{e.message}")
|
|
225
|
+
output.puts pastel.yellow("💡 Make sure to separate objects with a comma: diff object1, object2")
|
|
226
|
+
rescue => e
|
|
227
|
+
output.puts pastel.red("Error: #{e.message}")
|
|
228
|
+
output.puts pastel.yellow("💡 Use: diff object1, object2")
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Add aliases
|
|
234
|
+
begin
|
|
235
|
+
Pry.commands.alias_command 'nav', 'navigate'
|
|
236
|
+
Pry.commands.alias_command 'n', 'navigate'
|
|
237
|
+
rescue => e
|
|
238
|
+
# Silently fail if aliases can't be registered
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsolePro
|
|
4
|
+
# Rails integration via Railtie
|
|
5
|
+
class Railtie < Rails::Railtie
|
|
6
|
+
# Auto-load Rails Console Pro when Rails starts
|
|
7
|
+
# The initializer.rb file handles the actual setup
|
|
8
|
+
config.after_initialize do
|
|
9
|
+
# Ensure Pry hook is set (in case Pry loads after the gem)
|
|
10
|
+
if defined?(Pry)
|
|
11
|
+
# Wrap in a proc that handles errors gracefully
|
|
12
|
+
Pry.config.print = proc do |output, value, pry_instance|
|
|
13
|
+
begin
|
|
14
|
+
RailsConsolePro.call(output, value, pry_instance)
|
|
15
|
+
rescue => e
|
|
16
|
+
# Fallback to default Pry printing if our printer fails
|
|
17
|
+
if Rails.env.development? || ENV['RAILS_CONSOLE_PRO_DEBUG']
|
|
18
|
+
pastel = RailsConsolePro::ColorHelper.pastel
|
|
19
|
+
output.puts pastel.red.bold("💥 RailsConsolePro Error in Pry hook: #{e.class}: #{e.message}")
|
|
20
|
+
output.puts pastel.dim(e.backtrace.first(3).join("\n"))
|
|
21
|
+
end
|
|
22
|
+
Pry::ColorPrinter.default(output, value, pry_instance)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
Rails.logger&.debug("Rails Console Pro: Pry integration active")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsolePro
|
|
4
|
+
# Value object for schema inspection results
|
|
5
|
+
class SchemaInspectorResult
|
|
6
|
+
attr_reader :model
|
|
7
|
+
|
|
8
|
+
def initialize(model)
|
|
9
|
+
@model = model
|
|
10
|
+
validate_model!
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def ==(other)
|
|
14
|
+
other.is_a?(self.class) && other.model == model
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Export to JSON
|
|
18
|
+
def to_json(pretty: true)
|
|
19
|
+
FormatExporter.to_json(self, pretty: pretty)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Export to YAML
|
|
23
|
+
def to_yaml
|
|
24
|
+
FormatExporter.to_yaml(self)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Export to HTML
|
|
28
|
+
def to_html(style: :default)
|
|
29
|
+
FormatExporter.to_html(self, title: "Schema: #{model.name}", style: style)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Export to file
|
|
33
|
+
def export_to_file(file_path, format: nil)
|
|
34
|
+
FormatExporter.export_to_file(self, file_path, format: format)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def validate_model!
|
|
40
|
+
ModelValidator.validate_model_for_schema!(model)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsolePro
|
|
4
|
+
module Serializers
|
|
5
|
+
class ActiveRecordSerializer < BaseSerializer
|
|
6
|
+
def serialize(record)
|
|
7
|
+
{
|
|
8
|
+
'type' => 'active_record',
|
|
9
|
+
'class' => record.class.name,
|
|
10
|
+
'id' => record.id,
|
|
11
|
+
'attributes' => record.attributes,
|
|
12
|
+
'errors' => record.errors.any? ? record.errors.full_messages : nil
|
|
13
|
+
}.compact
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsolePro
|
|
4
|
+
module Serializers
|
|
5
|
+
class ArraySerializer < BaseSerializer
|
|
6
|
+
def serialize(array)
|
|
7
|
+
if array.all? { |item| item.is_a?(ActiveRecord::Base) }
|
|
8
|
+
{
|
|
9
|
+
'type' => 'active_record_collection',
|
|
10
|
+
'model' => array.first.class.name,
|
|
11
|
+
'count' => array.size,
|
|
12
|
+
'records' => array.map { |r| serialize_active_record(r) }
|
|
13
|
+
}
|
|
14
|
+
else
|
|
15
|
+
{
|
|
16
|
+
'type' => 'array',
|
|
17
|
+
'count' => array.size,
|
|
18
|
+
'items' => array.map { |item| serialize_data(item) }
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def serialize_active_record(record)
|
|
26
|
+
ActiveRecordSerializer.serialize(record, exporter)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsolePro
|
|
4
|
+
module Serializers
|
|
5
|
+
# Base serializer class
|
|
6
|
+
class BaseSerializer
|
|
7
|
+
def self.serialize(data, exporter)
|
|
8
|
+
new(exporter).serialize(data)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(exporter)
|
|
12
|
+
@exporter = exporter
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
protected
|
|
16
|
+
|
|
17
|
+
attr_reader :exporter
|
|
18
|
+
|
|
19
|
+
def serialize_data(data)
|
|
20
|
+
exporter.send(:serialize_data, data)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsolePro
|
|
4
|
+
module Serializers
|
|
5
|
+
class DiffSerializer < BaseSerializer
|
|
6
|
+
def serialize(result)
|
|
7
|
+
{
|
|
8
|
+
'type' => 'diff_comparison',
|
|
9
|
+
'object1_type' => result.object1_type,
|
|
10
|
+
'object2_type' => result.object2_type,
|
|
11
|
+
'identical' => result.identical,
|
|
12
|
+
'different_types' => result.different_types?,
|
|
13
|
+
'diff_count' => result.diff_count,
|
|
14
|
+
'has_differences' => result.has_differences?,
|
|
15
|
+
'differences' => serialize_data(result.differences),
|
|
16
|
+
'object1' => serialize_data(result.object1),
|
|
17
|
+
'object2' => serialize_data(result.object2),
|
|
18
|
+
'timestamp' => result.timestamp.iso8601
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsolePro
|
|
4
|
+
module Serializers
|
|
5
|
+
class ExplainSerializer < BaseSerializer
|
|
6
|
+
def serialize(result)
|
|
7
|
+
{
|
|
8
|
+
'type' => 'sql_explain',
|
|
9
|
+
'sql' => result.sql,
|
|
10
|
+
'execution_time' => result.execution_time,
|
|
11
|
+
'explain_output' => format_explain_output(result.explain_output),
|
|
12
|
+
'indexes_used' => result.indexes_used,
|
|
13
|
+
'recommendations' => result.recommendations,
|
|
14
|
+
'statistics' => result.statistics,
|
|
15
|
+
'slow_query' => result.slow_query?,
|
|
16
|
+
'has_indexes' => result.has_indexes?
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def format_explain_output(explain_output)
|
|
23
|
+
case explain_output
|
|
24
|
+
when String
|
|
25
|
+
explain_output
|
|
26
|
+
when Array
|
|
27
|
+
explain_output.map { |row| row.is_a?(Hash) ? row : row.to_s }
|
|
28
|
+
else
|
|
29
|
+
explain_output.inspect
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsolePro
|
|
4
|
+
module Serializers
|
|
5
|
+
class RelationSerializer < BaseSerializer
|
|
6
|
+
def serialize(relation)
|
|
7
|
+
records = relation.to_a
|
|
8
|
+
{
|
|
9
|
+
'type' => 'active_record_relation',
|
|
10
|
+
'model' => relation.klass.name,
|
|
11
|
+
'count' => records.size,
|
|
12
|
+
'records' => records.map { |r| serialize_active_record(r) },
|
|
13
|
+
'sql' => relation.to_sql
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def serialize_active_record(record)
|
|
20
|
+
ActiveRecordSerializer.serialize(record, exporter)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsolePro
|
|
4
|
+
module Serializers
|
|
5
|
+
class SchemaSerializer < BaseSerializer
|
|
6
|
+
def serialize(result)
|
|
7
|
+
model = result.model
|
|
8
|
+
{
|
|
9
|
+
'type' => 'schema_inspection',
|
|
10
|
+
'model' => model.name,
|
|
11
|
+
'table_name' => model.table_name,
|
|
12
|
+
'columns' => serialize_columns(model),
|
|
13
|
+
'indexes' => serialize_indexes(model),
|
|
14
|
+
'associations' => serialize_associations(model),
|
|
15
|
+
'validations' => serialize_validations(model),
|
|
16
|
+
'scopes' => serialize_scopes(model),
|
|
17
|
+
'database' => serialize_database_info(model)
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def serialize_columns(model)
|
|
24
|
+
model.columns.map do |column|
|
|
25
|
+
{
|
|
26
|
+
'name' => column.name.to_s,
|
|
27
|
+
'type' => column.type.to_s,
|
|
28
|
+
'null' => column.null,
|
|
29
|
+
'default' => column.default,
|
|
30
|
+
'limit' => column.limit,
|
|
31
|
+
'precision' => column.precision,
|
|
32
|
+
'scale' => column.scale
|
|
33
|
+
}.compact
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def serialize_indexes(model)
|
|
38
|
+
model.connection.indexes(model.table_name).map do |index|
|
|
39
|
+
where_clause = if index.where.is_a?(Regexp)
|
|
40
|
+
index.where.to_s
|
|
41
|
+
else
|
|
42
|
+
index.where
|
|
43
|
+
end
|
|
44
|
+
{
|
|
45
|
+
'name' => index.name.to_s,
|
|
46
|
+
'columns' => index.columns.map(&:to_s),
|
|
47
|
+
'unique' => index.unique,
|
|
48
|
+
'where' => where_clause
|
|
49
|
+
}.compact
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def serialize_associations(model)
|
|
54
|
+
associations = {}
|
|
55
|
+
%i[belongs_to has_one has_many has_and_belongs_to_many].each do |macro|
|
|
56
|
+
assocs = model.reflect_on_all_associations(macro)
|
|
57
|
+
next if assocs.empty?
|
|
58
|
+
|
|
59
|
+
associations[macro.to_s] = assocs.map do |assoc|
|
|
60
|
+
{
|
|
61
|
+
'name' => assoc.name.to_s,
|
|
62
|
+
'class_name' => assoc.class_name,
|
|
63
|
+
'foreign_key' => assoc.respond_to?(:foreign_key) ? assoc.foreign_key : nil,
|
|
64
|
+
'dependent' => assoc.options[:dependent]&.to_s,
|
|
65
|
+
'optional' => assoc.options[:optional],
|
|
66
|
+
'through' => assoc.options[:through]&.to_s,
|
|
67
|
+
'join_table' => assoc.respond_to?(:join_table) ? assoc.join_table : nil
|
|
68
|
+
}.compact
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
associations
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def serialize_validations(model)
|
|
75
|
+
validators_by_attr = model.validators.each_with_object({}) do |validator, hash|
|
|
76
|
+
validator.attributes.each do |attr|
|
|
77
|
+
attr_key = attr.is_a?(Symbol) ? attr.to_s : attr
|
|
78
|
+
(hash[attr_key] ||= []) << {
|
|
79
|
+
type: validator.class.name.split('::').last.gsub('Validator', ''),
|
|
80
|
+
options: serialize_options(validator.options)
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
validators_by_attr
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def serialize_options(options)
|
|
88
|
+
return nil if options.nil?
|
|
89
|
+
return options if options.empty?
|
|
90
|
+
|
|
91
|
+
options.each_with_object({}) do |(key, value), result|
|
|
92
|
+
string_key = key.is_a?(Symbol) ? key.to_s : key
|
|
93
|
+
result[string_key] = case value
|
|
94
|
+
when Symbol
|
|
95
|
+
value.to_s
|
|
96
|
+
when Regexp
|
|
97
|
+
value.to_s
|
|
98
|
+
when Array
|
|
99
|
+
value.map { |v| v.is_a?(Symbol) || v.is_a?(Regexp) ? v.to_s : serialize_data(v) }
|
|
100
|
+
else
|
|
101
|
+
serialize_data(value)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def serialize_scopes(model)
|
|
107
|
+
return [] unless model.respond_to?(:scopes) && model.scopes.any?
|
|
108
|
+
model.scopes.keys.map(&:to_s)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def serialize_database_info(model)
|
|
112
|
+
connection = model.connection
|
|
113
|
+
{
|
|
114
|
+
adapter: connection.adapter_name,
|
|
115
|
+
database: connection.respond_to?(:current_database) ? connection.current_database : nil
|
|
116
|
+
}.compact
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsolePro
|
|
4
|
+
module Serializers
|
|
5
|
+
class StatsSerializer < BaseSerializer
|
|
6
|
+
def serialize(result)
|
|
7
|
+
{
|
|
8
|
+
'type' => 'statistics',
|
|
9
|
+
'model' => result.model.name,
|
|
10
|
+
'record_count' => result.record_count,
|
|
11
|
+
'growth_rate' => result.growth_rate,
|
|
12
|
+
'table_size' => result.table_size,
|
|
13
|
+
'index_usage' => serialize_data(result.index_usage),
|
|
14
|
+
'column_stats' => serialize_data(result.column_stats),
|
|
15
|
+
'timestamp' => result.timestamp.iso8601,
|
|
16
|
+
'has_growth_data' => result.has_growth_data?,
|
|
17
|
+
'has_table_size' => result.has_table_size?,
|
|
18
|
+
'has_index_data' => result.has_index_data?
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|