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,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsolePro
|
|
4
|
+
module Printers
|
|
5
|
+
# Printer for SQL explain results
|
|
6
|
+
class ExplainPrinter < BasePrinter
|
|
7
|
+
SCAN_PATTERNS = {
|
|
8
|
+
'Seq Scan' => { color: :red, icon: 'ā ļø' },
|
|
9
|
+
'Index Scan' => { color: :green, icon: 'ā
' },
|
|
10
|
+
'Index Only Scan' => { color: :green, icon: 'ā
' },
|
|
11
|
+
'Hash Join' => { color: :yellow, icon: 'š' },
|
|
12
|
+
'Nested Loop' => { color: :yellow, icon: 'š' },
|
|
13
|
+
'Sort' => { color: :cyan, icon: 'š' }
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
def print
|
|
17
|
+
print_header
|
|
18
|
+
print_query
|
|
19
|
+
print_execution_time
|
|
20
|
+
print_query_plan
|
|
21
|
+
print_index_analysis
|
|
22
|
+
print_recommendations
|
|
23
|
+
print_statistics
|
|
24
|
+
print_footer
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def print_header
|
|
30
|
+
header_color = config.get_color(:header)
|
|
31
|
+
output.puts bold_color(header_color, "ā" * config.header_width)
|
|
32
|
+
output.puts bold_color(header_color, "š¬ SQL EXPLAIN ANALYSIS")
|
|
33
|
+
output.puts bold_color(header_color, "ā" * config.header_width)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def print_query
|
|
37
|
+
output.puts bold_color(config.get_color(:warning), "\nš Query:")
|
|
38
|
+
output.puts color(config.get_color(:attribute_value_string), value.sql)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def print_execution_time
|
|
42
|
+
return unless value.execution_time
|
|
43
|
+
|
|
44
|
+
time_str = format_execution_time(value.execution_time)
|
|
45
|
+
output.puts bold_color(config.get_color(:warning), "\nā±ļø Execution Time: ") + time_str
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def format_execution_time(time_ms)
|
|
49
|
+
case time_ms
|
|
50
|
+
when 0...10
|
|
51
|
+
color(config.get_color(:success), "#{time_ms.round(2)}ms")
|
|
52
|
+
when 10...100
|
|
53
|
+
color(config.get_color(:warning), "#{time_ms.round(2)}ms")
|
|
54
|
+
else
|
|
55
|
+
color(config.get_color(:error), "#{time_ms.round(2)}ms")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def print_query_plan
|
|
60
|
+
output.puts bold_color(config.get_color(:warning), "\nš Query Plan:")
|
|
61
|
+
|
|
62
|
+
case value.explain_output
|
|
63
|
+
when String
|
|
64
|
+
print_postgresql_plan(value.explain_output)
|
|
65
|
+
when Array
|
|
66
|
+
print_mysql_plan(value.explain_output)
|
|
67
|
+
else
|
|
68
|
+
output.puts color(config.get_color(:attribute_value_string), value.explain_output.inspect)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def print_postgresql_plan(explain_output)
|
|
73
|
+
explain_output.each_line do |line|
|
|
74
|
+
pattern_match = SCAN_PATTERNS.find { |pattern, _| line.include?(pattern) }
|
|
75
|
+
|
|
76
|
+
if pattern_match
|
|
77
|
+
pattern, pattern_config = pattern_match
|
|
78
|
+
output.puts " #{color(pattern_config[:color], pattern_config[:icon] + ' ' + line.strip)}"
|
|
79
|
+
else
|
|
80
|
+
output.puts " #{color(config.get_color(:attribute_value_string), line.strip)}"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def print_mysql_plan(explain_output)
|
|
86
|
+
explain_output.each do |row|
|
|
87
|
+
next unless row.is_a?(Hash)
|
|
88
|
+
|
|
89
|
+
row_id = row['id'] || row[:id] || '?'
|
|
90
|
+
output.puts color(config.get_color(:info), "\n Row #{row_id}:")
|
|
91
|
+
|
|
92
|
+
row.each do |key, val|
|
|
93
|
+
next if key.to_s == 'id'
|
|
94
|
+
key_color = config.get_color(:attribute_key)
|
|
95
|
+
output.puts " #{bold_color(key_color, key.to_s.ljust(15))} #{format_mysql_value(key, val)}"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def format_mysql_value(key, val)
|
|
101
|
+
case key.to_s
|
|
102
|
+
when 'type', 'select_type'
|
|
103
|
+
val.to_s.downcase.include?('all') ? color(config.get_color(:error), val.to_s) : color(config.get_color(:success), val.to_s)
|
|
104
|
+
when 'possible_keys', 'key'
|
|
105
|
+
val.nil? ? color(config.get_color(:error), 'NULL') : color(config.get_color(:success), val.to_s)
|
|
106
|
+
when 'rows'
|
|
107
|
+
val.to_i > 1000 ? color(config.get_color(:warning), val.to_s) : color(config.get_color(:success), val.to_s)
|
|
108
|
+
else
|
|
109
|
+
color(config.get_color(:attribute_value_string), val.to_s)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def print_index_analysis
|
|
114
|
+
output.puts bold_color(config.get_color(:warning), "\nš Index Analysis:")
|
|
115
|
+
|
|
116
|
+
if value.has_indexes?
|
|
117
|
+
output.puts color(config.get_color(:success), " ā
Indexes used:")
|
|
118
|
+
value.indexes_used.each do |index|
|
|
119
|
+
output.puts " ⢠#{color(config.get_color(:attribute_value_string), index)}"
|
|
120
|
+
end
|
|
121
|
+
else
|
|
122
|
+
output.puts color(config.get_color(:error), " ā ļø No indexes used - consider adding indexes for better performance")
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def print_recommendations
|
|
127
|
+
return if value.recommendations.empty?
|
|
128
|
+
|
|
129
|
+
output.puts bold_color(config.get_color(:warning), "\nš” Recommendations:")
|
|
130
|
+
value.recommendations.each do |rec|
|
|
131
|
+
output.puts " ⢠#{color(config.get_color(:attribute_value_string), rec)}"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def print_statistics
|
|
136
|
+
return if value.statistics.empty?
|
|
137
|
+
|
|
138
|
+
output.puts bold_color(config.get_color(:warning), "\nš Statistics:")
|
|
139
|
+
value.statistics.each do |key, val|
|
|
140
|
+
key_color = config.get_color(:attribute_key)
|
|
141
|
+
output.puts " #{bold_color(key_color, key.to_s.ljust(20))} #{color(config.get_color(:attribute_value_string), val.to_s)}"
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def print_footer
|
|
146
|
+
footer_color = config.get_color(:footer)
|
|
147
|
+
output.puts bold_color(footer_color, "ā" * config.header_width)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsolePro
|
|
4
|
+
module Printers
|
|
5
|
+
# Printer for ActiveRecord::Relation instances
|
|
6
|
+
class RelationPrinter < BasePrinter
|
|
7
|
+
def print
|
|
8
|
+
model_name = value.klass.name
|
|
9
|
+
|
|
10
|
+
# Use count for efficiency (doesn't load all records)
|
|
11
|
+
total_count = value.count
|
|
12
|
+
|
|
13
|
+
if total_count.zero?
|
|
14
|
+
output.puts color(config.get_color(:warning), "Empty #{model_name} collection")
|
|
15
|
+
return
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Use pagination with lazy loading - don't convert to array!
|
|
19
|
+
record_printer = proc { |record| ActiveRecordPrinter.new(output, record, pry_instance).print }
|
|
20
|
+
|
|
21
|
+
Paginator.new(output, value, total_count, config, record_printer).paginate
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsolePro
|
|
4
|
+
module Printers
|
|
5
|
+
# Printer for schema inspection results
|
|
6
|
+
class SchemaPrinter < BasePrinter
|
|
7
|
+
|
|
8
|
+
def print
|
|
9
|
+
model = value.model
|
|
10
|
+
print_header(model)
|
|
11
|
+
print_table_info(model)
|
|
12
|
+
print_columns(model)
|
|
13
|
+
print_indexes(model)
|
|
14
|
+
print_associations(model)
|
|
15
|
+
print_validations(model)
|
|
16
|
+
print_scopes(model)
|
|
17
|
+
print_database_info(model)
|
|
18
|
+
print_footer
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def print_header(model)
|
|
24
|
+
header_color = config.get_color(:header)
|
|
25
|
+
output.puts bold_color(header_color, "ā" * config.header_width)
|
|
26
|
+
output.puts bold_color(header_color, "š SCHEMA INSPECTOR: #{model.name}")
|
|
27
|
+
output.puts bold_color(header_color, "ā" * config.header_width)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def print_table_info(model)
|
|
31
|
+
table_name = ModelValidator.safe_table_name(model)
|
|
32
|
+
if table_name
|
|
33
|
+
output.puts bold_color(config.get_color(:warning), "\nš Table: ") + color(config.get_color(:attribute_value_string), table_name)
|
|
34
|
+
|
|
35
|
+
# Show STI indicator if applicable
|
|
36
|
+
if ModelValidator.sti_model?(model)
|
|
37
|
+
output.puts color(config.get_color(:info), " (Single Table Inheritance model - uses #{model.inheritance_column} column)")
|
|
38
|
+
end
|
|
39
|
+
else
|
|
40
|
+
output.puts bold_color(config.get_color(:error), "\nā ļø No table information available")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def print_columns(model)
|
|
45
|
+
columns = ModelValidator.safe_columns(model)
|
|
46
|
+
|
|
47
|
+
output.puts bold_color(config.get_color(:warning), "\nš§ Columns:")
|
|
48
|
+
if columns.any?
|
|
49
|
+
columns.each do |column|
|
|
50
|
+
type_color = config.get_type_color(column.type)
|
|
51
|
+
type_str = color(type_color, column.type.to_s.ljust(15))
|
|
52
|
+
null_str = column.null ? color(:dim, " (nullable)") : color(config.get_color(:error), " (NOT NULL)")
|
|
53
|
+
default_str = column.default ? color(:dim, " default: #{column.default}") : ""
|
|
54
|
+
|
|
55
|
+
key_color = config.get_color(:attribute_key)
|
|
56
|
+
output.puts " #{bold_color(key_color, column.name.to_s.ljust(25))} #{type_str}#{null_str}#{default_str}"
|
|
57
|
+
end
|
|
58
|
+
else
|
|
59
|
+
output.puts color(:dim, " No columns available")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def print_indexes(model)
|
|
64
|
+
output.puts bold_color(config.get_color(:warning), "\nš Indexes:")
|
|
65
|
+
indexes = ModelValidator.safe_indexes(model)
|
|
66
|
+
|
|
67
|
+
if indexes.any?
|
|
68
|
+
indexes.each do |index|
|
|
69
|
+
unique_str = index.unique ? color(config.get_color(:success), " UNIQUE") : ""
|
|
70
|
+
columns_str = color(config.get_color(:attribute_value_string), "(#{index.columns.join(', ')})")
|
|
71
|
+
key_color = config.get_color(:attribute_key)
|
|
72
|
+
output.puts " #{bold_color(key_color, index.name.ljust(30))}#{columns_str}#{unique_str}"
|
|
73
|
+
end
|
|
74
|
+
else
|
|
75
|
+
output.puts color(:dim, " No indexes defined")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def print_associations(model)
|
|
80
|
+
output.puts bold_color(config.get_color(:warning), "\nš Associations:")
|
|
81
|
+
|
|
82
|
+
print_association_group(model, :belongs_to, "belongs_to", "āļø")
|
|
83
|
+
print_association_group(model, :has_one, "has_one", "ā")
|
|
84
|
+
print_association_group(model, :has_many, "has_many", "ā")
|
|
85
|
+
print_association_group(model, :has_and_belongs_to_many, "has_and_belongs_to_many", "ā")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def print_association_group(model, macro, label, icon)
|
|
89
|
+
associations = ModelValidator.safe_associations(model, macro)
|
|
90
|
+
return if associations.empty?
|
|
91
|
+
|
|
92
|
+
output.puts color(config.get_color(:info), " #{label}:")
|
|
93
|
+
associations.each do |assoc|
|
|
94
|
+
# Validate association before displaying
|
|
95
|
+
unless ModelValidator.valid_associations?(model, assoc.name)
|
|
96
|
+
key_color = config.get_color(:attribute_key)
|
|
97
|
+
output.puts " #{icon} #{bold_color(key_color, assoc.name.to_s)} ā #{color(config.get_color(:error), 'INVALID (associated class not found)')}"
|
|
98
|
+
next
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
class_name = assoc.class_name
|
|
102
|
+
details = format_association_details(assoc)
|
|
103
|
+
key_color = config.get_color(:attribute_key)
|
|
104
|
+
output.puts " #{icon} #{bold_color(key_color, assoc.name.to_s)} ā #{color(config.get_color(:attribute_value_string), class_name)}#{details}"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def format_association_details(assoc)
|
|
109
|
+
details = []
|
|
110
|
+
details << color(:dim, " [#{assoc.foreign_key}]") if assoc.respond_to?(:foreign_key)
|
|
111
|
+
details << color(:dim, " (optional)") if assoc.options[:optional]
|
|
112
|
+
details << color(:yellow, " (#{assoc.options[:dependent]})") if assoc.options[:dependent]
|
|
113
|
+
details << color(:magenta, " through :#{assoc.options[:through]}") if assoc.options[:through]
|
|
114
|
+
details << color(:dim, " [#{assoc.join_table}]") if assoc.respond_to?(:join_table) && assoc.join_table
|
|
115
|
+
details.join
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def print_validations(model)
|
|
119
|
+
output.puts bold_color(config.get_color(:warning), "\nā
Validations:")
|
|
120
|
+
validators = model.validators
|
|
121
|
+
|
|
122
|
+
if validators.any?
|
|
123
|
+
validators_by_attr = group_validators_by_attribute(validators)
|
|
124
|
+
validators_by_attr.sort.each do |attr, attrs_validators|
|
|
125
|
+
validation_str = format_validations(attrs_validators)
|
|
126
|
+
key_color = config.get_color(:attribute_key)
|
|
127
|
+
output.puts " #{bold_color(key_color, attr.to_s.ljust(25))} #{validation_str}"
|
|
128
|
+
end
|
|
129
|
+
else
|
|
130
|
+
output.puts color(:dim, " No validations defined")
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def group_validators_by_attribute(validators)
|
|
135
|
+
validators.each_with_object({}) do |validator, hash|
|
|
136
|
+
validator.attributes.each do |attr|
|
|
137
|
+
(hash[attr] ||= []) << validator
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def format_validations(validators)
|
|
143
|
+
validators.map do |validator|
|
|
144
|
+
validator_type = extract_validator_type(validator)
|
|
145
|
+
color_method = config.get_validator_color(validator_type)
|
|
146
|
+
options_str = format_validator_options(validator)
|
|
147
|
+
color(color_method, "#{validator_type.downcase}#{options_str}")
|
|
148
|
+
end.join(', ')
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def extract_validator_type(validator)
|
|
152
|
+
validator.class.name.split('::').last.gsub('Validator', '')
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def format_validator_options(validator)
|
|
156
|
+
options = []
|
|
157
|
+
options << "min: #{validator.options[:minimum]}" if validator.options[:minimum]
|
|
158
|
+
options << "max: #{validator.options[:maximum]}" if validator.options[:maximum]
|
|
159
|
+
options.any? ? "(#{options.join(', ')})" : ""
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def print_scopes(model)
|
|
163
|
+
return unless model.respond_to?(:scopes) && model.scopes.any?
|
|
164
|
+
|
|
165
|
+
output.puts bold_color(config.get_color(:warning), "\nšÆ Scopes:")
|
|
166
|
+
model.scopes.keys.each do |scope|
|
|
167
|
+
key_color = config.get_color(:attribute_key)
|
|
168
|
+
output.puts " #{bold_color(key_color, scope.to_s)}"
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def print_database_info(model)
|
|
173
|
+
output.puts bold_color(config.get_color(:warning), "\nšļø Database:")
|
|
174
|
+
connection = model.connection
|
|
175
|
+
output.puts " #{color(:dim, 'Adapter:')} #{color(config.get_color(:attribute_value_string), connection.adapter_name)}"
|
|
176
|
+
|
|
177
|
+
if connection.respond_to?(:current_database)
|
|
178
|
+
output.puts " #{color(:dim, 'Database:')} #{color(config.get_color(:attribute_value_string), connection.current_database)}"
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def print_footer
|
|
183
|
+
footer_color = config.get_color(:footer)
|
|
184
|
+
output.puts bold_color(footer_color, "ā" * config.header_width)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsConsolePro
|
|
4
|
+
module Printers
|
|
5
|
+
# Printer for model statistics
|
|
6
|
+
class StatsPrinter < BasePrinter
|
|
7
|
+
def print
|
|
8
|
+
print_header
|
|
9
|
+
print_record_count
|
|
10
|
+
print_growth_rate
|
|
11
|
+
print_table_size
|
|
12
|
+
print_index_usage
|
|
13
|
+
print_column_stats
|
|
14
|
+
print_footer
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def print_header
|
|
20
|
+
model = value.model
|
|
21
|
+
header_color = config.get_color(:header)
|
|
22
|
+
output.puts bold_color(header_color, "ā" * config.header_width)
|
|
23
|
+
output.puts bold_color(header_color, "š MODEL STATISTICS: #{model.name}")
|
|
24
|
+
output.puts bold_color(header_color, "ā" * config.header_width)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def print_record_count
|
|
28
|
+
output.puts bold_color(config.get_color(:warning), "\nš Record Count:")
|
|
29
|
+
count = value.record_count
|
|
30
|
+
count_color = count > 0 ? config.get_color(:success) : config.get_color(:info)
|
|
31
|
+
output.puts " #{bold_color(count_color, count.to_s)} #{pluralize(count, 'record')}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def print_growth_rate
|
|
35
|
+
return unless value.has_growth_data?
|
|
36
|
+
|
|
37
|
+
output.puts bold_color(config.get_color(:warning), "\nš Growth Rate:")
|
|
38
|
+
rate = value.growth_rate
|
|
39
|
+
rate_str = format_percentage(rate)
|
|
40
|
+
rate_color = rate > 0 ? config.get_color(:success) : rate < 0 ? config.get_color(:error) : config.get_color(:info)
|
|
41
|
+
trend = rate > 0 ? "ā" : rate < 0 ? "ā" : "ā"
|
|
42
|
+
output.puts " #{bold_color(rate_color, "#{trend} #{rate_str}")}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def print_table_size
|
|
46
|
+
return unless value.has_table_size?
|
|
47
|
+
|
|
48
|
+
output.puts bold_color(config.get_color(:warning), "\nš¾ Table Size:")
|
|
49
|
+
size = value.table_size
|
|
50
|
+
size_str = format_bytes(size)
|
|
51
|
+
output.puts " #{bold_color(config.get_color(:attribute_value_numeric), size_str)}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def print_index_usage
|
|
55
|
+
return unless value.has_index_data?
|
|
56
|
+
|
|
57
|
+
output.puts bold_color(config.get_color(:warning), "\nš Index Usage:")
|
|
58
|
+
value.index_usage.each do |index_name, usage_info|
|
|
59
|
+
key_color = config.get_color(:attribute_key)
|
|
60
|
+
usage_str = format_index_usage(usage_info)
|
|
61
|
+
output.puts " #{bold_color(key_color, index_name.to_s.ljust(30))} #{usage_str}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def print_column_stats
|
|
66
|
+
return if value.column_stats.empty?
|
|
67
|
+
|
|
68
|
+
output.puts bold_color(config.get_color(:warning), "\nš Column Statistics:")
|
|
69
|
+
value.column_stats.each do |column_name, stats|
|
|
70
|
+
key_color = config.get_color(:attribute_key)
|
|
71
|
+
stats_str = format_column_stats(stats)
|
|
72
|
+
output.puts " #{bold_color(key_color, column_name.to_s.ljust(25))} #{stats_str}"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def print_footer
|
|
77
|
+
footer_color = config.get_color(:footer)
|
|
78
|
+
output.puts bold_color(footer_color, "\n" + "ā" * config.header_width)
|
|
79
|
+
output.puts color(:dim, "Generated: #{value.timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def pluralize(count, word)
|
|
83
|
+
count == 1 ? word : "#{word}s"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def format_percentage(value)
|
|
87
|
+
sign = value > 0 ? "+" : ""
|
|
88
|
+
"#{sign}#{value.round(2)}%"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def format_bytes(bytes)
|
|
92
|
+
return "0 B" if bytes.nil? || bytes == 0
|
|
93
|
+
|
|
94
|
+
units = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
95
|
+
index = 0
|
|
96
|
+
size = bytes.to_f
|
|
97
|
+
|
|
98
|
+
while size >= 1024 && index < units.length - 1
|
|
99
|
+
size /= 1024
|
|
100
|
+
index += 1
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
"#{size.round(2)} #{units[index]}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def format_index_usage(usage_info)
|
|
107
|
+
case usage_info
|
|
108
|
+
when Hash
|
|
109
|
+
parts = []
|
|
110
|
+
parts << color(config.get_color(:success), "used") if usage_info[:used]
|
|
111
|
+
parts << color(config.get_color(:info), "#{usage_info[:scans]} scans") if usage_info[:scans]
|
|
112
|
+
parts << color(config.get_color(:warning), "#{usage_info[:rows]} rows") if usage_info[:rows]
|
|
113
|
+
parts.any? ? parts.join(", ") : color(:dim, "not used")
|
|
114
|
+
when String
|
|
115
|
+
color(config.get_color(:attribute_value_string), usage_info)
|
|
116
|
+
else
|
|
117
|
+
color(:dim, "unknown")
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def format_column_stats(stats)
|
|
122
|
+
parts = []
|
|
123
|
+
parts << color(config.get_color(:info), "nulls: #{stats[:null_count]}") if stats[:null_count]
|
|
124
|
+
parts << color(config.get_color(:attribute_value_numeric), "distinct: #{stats[:distinct_count]}") if stats[:distinct_count]
|
|
125
|
+
parts.any? ? parts.join(", ") : color(:dim, "no stats")
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|