rails-health-checker 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.
@@ -0,0 +1,499 @@
1
+ module RailsHealthChecker
2
+ class ReportGenerator
3
+ def initialize(results)
4
+ @results = results
5
+ @timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
6
+ @gem_details = fetch_gem_changelogs
7
+ end
8
+
9
+ def generate_markdown
10
+ <<~MARKDOWN
11
+ # Rails Health Check Report
12
+
13
+ **Generated:** #{@timestamp}
14
+ **Project:** #{project_name}
15
+
16
+ ## 📊 Summary
17
+
18
+ | Component | Status | Details |
19
+ |-----------|--------|---------|
20
+ | Rails | #{status_emoji(@results[:rails_version][:status])} #{@results[:rails_version][:status]} | v#{@results[:rails_version][:current]} |
21
+ | Ruby | #{status_emoji(@results[:ruby_version][:status])} #{@results[:ruby_version][:status]} | v#{@results[:ruby_version][:current]} |
22
+ | Database | #{status_emoji(@results[:database][:status])} #{@results[:database][:status]} | #{@results[:database][:connected] ? 'Connected' : 'Disconnected'} |
23
+ | Gems | #{gem_status_emoji} #{gem_status_text} | #{@results[:gems][:total]} total, #{@results[:gems][:outdated]} outdated |
24
+ | Security | #{status_emoji(@results[:security][:status])} #{@results[:security][:status]} | #{@results[:security][:outdated_count]} outdated gems |
25
+ | Background Jobs | #{status_emoji(@results[:jobs][:status])} #{@results[:jobs][:status]} | #{job_summary_text} |
26
+
27
+ ## 🔧 Rails Environment
28
+
29
+ - **Rails Version:** #{@results[:rails_version][:current]}
30
+ - **Ruby Version:** #{@results[:ruby_version][:current]}
31
+ - **Environment:** #{Rails.env rescue 'Unknown'}
32
+ - **Cache Store:** #{@results[:system][:server_requirements][:cache_store][:type].split('::').last}
33
+ - #{@results[:system][:server_requirements][:cache_store][:explanation]}
34
+ - 💡 **Recommendation:** #{@results[:system][:server_requirements][:cache_store][:recommendation]}
35
+
36
+ ## 💾 Database
37
+
38
+ - **Status:** #{@results[:database][:status]}
39
+ - **Connected:** #{@results[:database][:connected] ? '✅ Yes' : '❌ No'}
40
+ #{database_error_section}
41
+
42
+ ## 📦 Gem Dependencies
43
+
44
+ - **Total Gems:** #{@results[:gems][:total]}
45
+ - **Outdated Gems:** #{@results[:gems][:outdated]}
46
+ - **Vulnerable Gems:** #{@results[:gems][:vulnerable]}
47
+
48
+ #{outdated_gems_section}
49
+
50
+ ## 🔒 Security Analysis
51
+
52
+ - **Status:** #{@results[:security][:status]}
53
+ - **Outdated Packages:** #{@results[:security][:outdated_count]}
54
+
55
+ #{security_recommendations}
56
+
57
+ ## ⚙️ Background Jobs
58
+
59
+ #{background_jobs_section}
60
+
61
+ ## 📋 Detailed Gem Analysis
62
+
63
+ #{detailed_gem_analysis}
64
+
65
+ ## 📝 Changelog & Benefits
66
+
67
+ #{changelog_section}
68
+
69
+ ## ⚡ Performance Impact
70
+
71
+ #{performance_analysis}
72
+
73
+ ## 🎯 Priority Actions
74
+
75
+ #{priority_actions}
76
+
77
+ ## 📈 Recommendations
78
+
79
+ #{generate_recommendations}
80
+
81
+ ## 📊 Health Score
82
+
83
+ #{calculate_health_score}
84
+
85
+ ---
86
+ *Report generated by RailsHealthChecker v#{RailsHealthChecker::VERSION}*
87
+ MARKDOWN
88
+ end
89
+
90
+ def save_to_file(filename = nil)
91
+ filename ||= "rails_health_report.md"
92
+ File.write(filename, generate_markdown)
93
+ filename
94
+ end
95
+
96
+ private
97
+
98
+ def project_name
99
+ File.basename(Dir.pwd)
100
+ end
101
+
102
+ def status_emoji(status)
103
+ case status.to_s
104
+ when 'healthy', 'secure' then '✅'
105
+ when 'outdated', 'needs_attention' then '⚠️'
106
+ when 'unhealthy' then '❌'
107
+ else '❓'
108
+ end
109
+ end
110
+
111
+ def gem_status_emoji
112
+ @results[:gems][:outdated] > 0 ? '⚠️' : '✅'
113
+ end
114
+
115
+ def gem_status_text
116
+ @results[:gems][:outdated] > 0 ? 'needs_attention' : 'healthy'
117
+ end
118
+
119
+ def database_error_section
120
+ return '' unless @results[:database][:error]
121
+ "\n- **Error:** #{@results[:database][:error]}"
122
+ end
123
+
124
+ def outdated_gems_section
125
+ return '' if @results[:gems][:outdated] == 0
126
+
127
+ "\n### Outdated Gems Details\n\n" +
128
+ @results[:gems][:details].select { |gem| gem[:outdated] }
129
+ .map { |gem| "- **#{gem[:name]}** (#{gem[:version]})" }
130
+ .join("\n")
131
+ end
132
+
133
+ def security_recommendations
134
+ if @results[:security][:outdated_count] > 0
135
+ "\n⚠️ **Action Required:** Update outdated gems to latest versions"
136
+ else
137
+ "\n✅ **Good:** All gems are up to date"
138
+ end
139
+ end
140
+
141
+ def generate_recommendations
142
+ recommendations = []
143
+
144
+ recommendations << "- Update Rails to latest version" if @results[:rails_version][:status] == 'outdated'
145
+ recommendations << "- Update Ruby to latest version" if @results[:ruby_version][:status] == 'outdated'
146
+ recommendations << "- Fix database connection issues" if @results[:database][:status] == 'unhealthy'
147
+ recommendations << "- Run `bundle update` to update outdated gems" if @results[:gems][:outdated] > 0
148
+ recommendations << "- Review and update security-vulnerable packages" if @results[:security][:outdated_count] > 0
149
+
150
+ recommendations.empty? ? "✅ **Excellent!** Your Rails application is healthy." : recommendations.join("\n")
151
+ end
152
+
153
+ def fetch_gem_changelogs
154
+ outdated_gems = @results[:gems][:details].select { |gem| gem[:outdated] }
155
+ outdated_gems.map do |gem|
156
+ {
157
+ name: gem[:name],
158
+ current_version: gem[:version],
159
+ benefits: get_update_benefits(gem[:name]),
160
+ changelog_url: get_changelog_url(gem[:name])
161
+ }
162
+ end
163
+ end
164
+
165
+ def get_update_benefits(gem_name)
166
+ benefits = {
167
+ 'rails' => ['Security patches', 'Performance improvements', 'New features', 'Bug fixes'],
168
+ 'pg' => ['Database performance', 'Connection stability', 'Security updates'],
169
+ 'puma' => ['Server performance', 'Memory usage optimization', 'Security fixes'],
170
+ 'bootsnap' => ['Faster boot times', 'Reduced memory usage'],
171
+ 'turbo-rails' => ['Better SPA experience', 'Performance improvements'],
172
+ 'stimulus-rails' => ['Enhanced JavaScript framework', 'Better DOM manipulation']
173
+ }
174
+ benefits[gem_name] || ['Security updates', 'Bug fixes', 'Performance improvements']
175
+ end
176
+
177
+ def get_changelog_url(gem_name)
178
+ "https://github.com/search?q=#{gem_name}+changelog&type=repositories"
179
+ end
180
+
181
+ def detailed_gem_analysis
182
+ return "All gems are up to date! ✅" if @results[:gems][:outdated] == 0
183
+
184
+ analysis = []
185
+ critical_gems = ['rails', 'pg', 'puma', 'bootsnap']
186
+
187
+ @results[:gems][:details].select { |gem| gem[:outdated] }.each do |gem|
188
+ priority = critical_gems.include?(gem[:name]) ? '🔴 HIGH' : '🟡 MEDIUM'
189
+ analysis << "### #{gem[:name]} #{priority}"
190
+ analysis << "- **Current Version:** #{gem[:version]}"
191
+ analysis << "- **Status:** Outdated"
192
+ analysis << "- **Impact:** #{get_gem_impact(gem[:name])}"
193
+ analysis << ""
194
+ end
195
+
196
+ analysis.join("\n")
197
+ end
198
+
199
+ def get_gem_impact(gem_name)
200
+ impacts = {
201
+ 'rails' => 'Core framework - affects entire application',
202
+ 'pg' => 'Database connectivity and performance',
203
+ 'puma' => 'Web server performance and stability',
204
+ 'bootsnap' => 'Application boot time and caching',
205
+ 'turbo-rails' => 'Frontend performance and user experience',
206
+ 'stimulus-rails' => 'JavaScript functionality'
207
+ }
208
+ impacts[gem_name] || 'General application functionality'
209
+ end
210
+
211
+ def changelog_section
212
+ return "No outdated gems found. ✅" if @gem_details.empty?
213
+
214
+ changelog = []
215
+ @gem_details.each do |gem|
216
+ changelog << "### #{gem[:name]}"
217
+ changelog << "**Benefits of updating:**"
218
+ gem[:benefits].each { |benefit| changelog << "- #{benefit}" }
219
+ changelog << "**Changelog:** [View on GitHub](#{gem[:changelog_url]})"
220
+ changelog << ""
221
+ end
222
+
223
+ changelog.join("\n")
224
+ end
225
+
226
+ def performance_analysis
227
+ score = calculate_performance_score
228
+
229
+ analysis = []
230
+ analysis << "**Overall Performance Score:** #{score}/100"
231
+ analysis << ""
232
+
233
+ if @results[:rails_version][:status] == 'outdated'
234
+ analysis << "- ⚠️ **Rails Version Impact:** Outdated Rails may have performance penalties"
235
+ end
236
+
237
+ if @results[:gems][:outdated] > 10
238
+ analysis << "- ⚠️ **Gem Dependencies:** High number of outdated gems may impact performance"
239
+ end
240
+
241
+ if @results[:database][:status] == 'unhealthy'
242
+ analysis << "- ❌ **Database Performance:** Connection issues will severely impact performance"
243
+ end
244
+
245
+ analysis << "- ✅ **Optimization Tip:** Keep gems updated for best performance" if @results[:gems][:outdated] > 0
246
+
247
+ analysis.join("\n")
248
+ end
249
+
250
+ def calculate_performance_score
251
+ score = 100
252
+ score -= 20 if @results[:rails_version][:status] == 'outdated'
253
+ score -= 15 if @results[:ruby_version][:status] == 'outdated'
254
+ score -= 30 if @results[:database][:status] == 'unhealthy'
255
+ score -= (@results[:gems][:outdated] * 2) # 2 points per outdated gem
256
+ score -= (@results[:security][:outdated_count] * 3) # 3 points per security issue
257
+ [score, 0].max
258
+ end
259
+
260
+ def priority_actions
261
+ actions = []
262
+
263
+ if @results[:database][:status] == 'unhealthy'
264
+ actions << "🔴 **CRITICAL:** Fix database connection immediately"
265
+ end
266
+
267
+ if @results[:rails_version][:status] == 'outdated'
268
+ actions << "🟠 **HIGH:** Update Rails framework"
269
+ end
270
+
271
+ if @results[:security][:outdated_count] > 5
272
+ actions << "🟠 **HIGH:** Address security vulnerabilities"
273
+ end
274
+
275
+ if @results[:gems][:outdated] > 10
276
+ actions << "🟡 **MEDIUM:** Update outdated gems"
277
+ end
278
+
279
+ if @results[:ruby_version][:status] == 'outdated'
280
+ actions << "🟡 **MEDIUM:** Consider Ruby version upgrade"
281
+ end
282
+
283
+ actions.empty? ? "✅ No critical actions required!" : actions.join("\n")
284
+ end
285
+
286
+ def calculate_health_score
287
+ score = calculate_performance_score
288
+ reasons = get_score_reasons
289
+
290
+ status = case score
291
+ when 90..100 then "✅ Excellent"
292
+ when 70..89 then "🟡 Good"
293
+ when 50..69 then "🟠 Needs Attention"
294
+ else "🔴 Critical"
295
+ end
296
+
297
+ result = "🏆 **Overall Health Score: #{score}/100 (#{status})**\n\n"
298
+
299
+ if score < 70
300
+ result += "**⚠️ Critical Issues Detected:**\n#{reasons[:critical].join("\n")}\n\n" unless reasons[:critical].empty?
301
+ result += "**🟡 Warning Issues:**\n#{reasons[:warnings].join("\n")}\n\n" unless reasons[:warnings].empty?
302
+ end
303
+
304
+ result += "**Score Breakdown:**\n" +
305
+ "- Rails Version: #{@results[:rails_version][:status] == 'healthy' ? '+20' : '-20'} #{rails_score_reason}\n" +
306
+ "- Ruby Version: #{@results[:ruby_version][:status] == 'healthy' ? '+15' : '-15'} #{ruby_score_reason}\n" +
307
+ "- Database: #{@results[:database][:status] == 'healthy' ? '+30' : '-30'} #{database_score_reason}\n" +
308
+ "- Gem Dependencies: -#{@results[:gems][:outdated] * 2} (#{@results[:gems][:outdated]} outdated gems)\n" +
309
+ "- Security: -#{@results[:security][:outdated_count] * 3} (#{@results[:security][:outdated_count]} security issues)\n" +
310
+ "- Background Jobs: #{job_score_impact} #{job_score_reason}\n\n" +
311
+ "**Improvement Suggestions:**\n#{get_improvement_suggestions.join("\n")}"
312
+
313
+ result
314
+ end
315
+
316
+ def job_summary_text
317
+ jobs = @results[:jobs]
318
+ return 'Not configured' if jobs[:status] == 'not_configured'
319
+ return 'Error' if jobs[:status] == 'error'
320
+
321
+ parts = []
322
+ parts << "Sidekiq: #{jobs[:sidekiq][:status]}" if jobs[:sidekiq][:available]
323
+ parts << "Resque: #{jobs[:resque][:status]}" if jobs[:resque][:available]
324
+ parts << "ActiveJob: #{jobs[:active_job][:status]}" if jobs[:active_job][:available]
325
+
326
+ parts.empty? ? 'Not configured' : parts.join(', ')
327
+ end
328
+
329
+ def background_jobs_section
330
+ jobs = @results[:jobs]
331
+ return "No background job systems detected." if jobs[:status] == 'not_configured'
332
+
333
+ sections = []
334
+
335
+ if jobs[:sidekiq][:available]
336
+ sections << sidekiq_section(jobs[:sidekiq])
337
+ end
338
+
339
+ if jobs[:resque][:available]
340
+ sections << resque_section(jobs[:resque])
341
+ end
342
+
343
+ if jobs[:active_job][:available]
344
+ sections << active_job_section(jobs[:active_job])
345
+ end
346
+
347
+ sections.join("\n\n")
348
+ end
349
+
350
+ def sidekiq_section(sidekiq)
351
+ return "### Sidekiq\n- **Status:** Error (#{sidekiq[:error]})" if sidekiq[:error]
352
+
353
+ <<~SECTION
354
+ ### Sidekiq
355
+ - **Status:** #{sidekiq[:status].capitalize}
356
+ - **Processed Jobs:** #{sidekiq[:processed]}
357
+ - **Failed Jobs:** #{sidekiq[:failed]}
358
+ - **Enqueued Jobs:** #{sidekiq[:enqueued]}
359
+ - **Retry Queue:** #{sidekiq[:retry_size]}
360
+ - **Dead Queue:** #{sidekiq[:dead_size]}
361
+ - **Active Workers:** #{sidekiq[:workers]}
362
+ - **Queues:** #{sidekiq[:queues].size}
363
+ SECTION
364
+ end
365
+
366
+ def resque_section(resque)
367
+ return "### Resque\n- **Status:** Error (#{resque[:error]})" if resque[:error]
368
+
369
+ <<~SECTION
370
+ ### Resque
371
+ - **Status:** #{resque[:status].capitalize}
372
+ - **Pending Jobs:** #{resque[:pending]}
373
+ - **Processed Jobs:** #{resque[:processed]}
374
+ - **Failed Jobs:** #{resque[:failed]}
375
+ - **Workers:** #{resque[:workers]}
376
+ - **Working:** #{resque[:working]}
377
+ - **Queues:** #{resque[:queues]}
378
+ SECTION
379
+ end
380
+
381
+ def active_job_section(active_job)
382
+ return "### ActiveJob\n- **Status:** Error (#{active_job[:error]})" if active_job[:error]
383
+
384
+ <<~SECTION
385
+ ### ActiveJob
386
+ - **Status:** #{active_job[:status].capitalize}
387
+ - **Adapter:** #{active_job[:adapter]}
388
+ SECTION
389
+ end
390
+
391
+ def job_score_impact
392
+ case @results[:jobs][:status]
393
+ when 'critical' then '-15'
394
+ when 'warning' then '-10'
395
+ when 'error' then '-20'
396
+ when 'not_configured' then '+0'
397
+ else '+5'
398
+ end
399
+ end
400
+
401
+ def get_score_reasons
402
+ critical = []
403
+ warnings = []
404
+
405
+ # Critical issues (major score impact)
406
+ if @results[:database][:status] == 'unhealthy'
407
+ critical << "❌ **Database Connection Failed** - Application cannot function without database access"
408
+ end
409
+
410
+ if @results[:jobs][:status] == 'critical'
411
+ critical << "❌ **Background Jobs System Critical** - Job processing is severely impacted"
412
+ end
413
+
414
+ if @results[:rails_version][:status] == 'outdated'
415
+ critical << "⚠️ **Outdated Rails Version** - Security and performance risks"
416
+ end
417
+
418
+ # Warning issues (moderate score impact)
419
+ if @results[:gems][:outdated] > 20
420
+ warnings << "🟡 **Many Outdated Gems** - #{@results[:gems][:outdated]} gems need updates"
421
+ end
422
+
423
+ if @results[:security][:outdated_count] > 10
424
+ warnings << "🟡 **Security Vulnerabilities** - #{@results[:security][:outdated_count]} potential security issues"
425
+ end
426
+
427
+ if @results[:ruby_version][:status] == 'outdated'
428
+ warnings << "🟡 **Outdated Ruby Version** - Consider upgrading for better performance"
429
+ end
430
+
431
+ { critical: critical, warnings: warnings }
432
+ end
433
+
434
+ def rails_score_reason
435
+ case @results[:rails_version][:status]
436
+ when 'healthy' then '(Current and supported)'
437
+ when 'outdated' then '(Outdated - security risk)'
438
+ else '(Unknown status)'
439
+ end
440
+ end
441
+
442
+ def ruby_score_reason
443
+ case @results[:ruby_version][:status]
444
+ when 'healthy' then '(Current and supported)'
445
+ when 'outdated' then '(Outdated - performance impact)'
446
+ else '(Unknown status)'
447
+ end
448
+ end
449
+
450
+ def database_score_reason
451
+ case @results[:database][:status]
452
+ when 'healthy' then '(Connected and responsive)'
453
+ when 'unhealthy' then '(Connection failed - critical issue)'
454
+ else '(Unknown status)'
455
+ end
456
+ end
457
+
458
+ def job_score_reason
459
+ case @results[:jobs][:status]
460
+ when 'healthy' then '(All job systems operational)'
461
+ when 'warning' then '(High queue sizes detected)'
462
+ when 'critical' then '(Job system failures detected)'
463
+ when 'error' then '(Job system errors)'
464
+ when 'not_configured' then '(No background job system)'
465
+ else '(Unknown status)'
466
+ end
467
+ end
468
+
469
+ def get_improvement_suggestions
470
+ suggestions = []
471
+
472
+ if @results[:database][:status] == 'unhealthy'
473
+ suggestions << "1. 🔴 **URGENT**: Fix database connection - check credentials, network, and database server status"
474
+ end
475
+
476
+ if @results[:rails_version][:status] == 'outdated'
477
+ suggestions << "2. 🟠 **HIGH**: Update Rails to latest stable version for security patches"
478
+ end
479
+
480
+ if @results[:gems][:outdated] > 10
481
+ suggestions << "3. 🟡 **MEDIUM**: Run 'bundle update' to update outdated gems"
482
+ end
483
+
484
+ if @results[:security][:outdated_count] > 5
485
+ suggestions << "4. 🟡 **MEDIUM**: Review and update gems with security vulnerabilities"
486
+ end
487
+
488
+ if @results[:jobs][:status] == 'critical'
489
+ suggestions << "5. 🔴 **URGENT**: Check background job system configuration and restart workers"
490
+ end
491
+
492
+ if suggestions.empty?
493
+ suggestions << "✅ **Great!** Your application is in excellent health. Keep monitoring regularly."
494
+ end
495
+
496
+ suggestions
497
+ end
498
+ end
499
+ end
@@ -0,0 +1,182 @@
1
+ module RailsHealthChecker
2
+ class SystemAnalyzer
3
+ def analyze
4
+ {
5
+ rails_info: rails_system_info,
6
+ required_libs: check_required_libraries,
7
+ optional_libs: check_optional_libraries,
8
+ server_requirements: check_server_requirements,
9
+ cable_info: check_action_cable,
10
+ environment_info: environment_details
11
+ }
12
+ end
13
+
14
+ private
15
+
16
+ def rails_system_info
17
+ {
18
+ version: Rails.version,
19
+ environment: Rails.env,
20
+ root: Rails.root.to_s,
21
+ config_loaded: Rails.application.config.loaded?,
22
+ eager_load: Rails.application.config.eager_load,
23
+ cache_classes: Rails.application.config.cache_classes
24
+ }
25
+ rescue => e
26
+ { error: e.message }
27
+ end
28
+
29
+ def check_required_libraries
30
+ required = {
31
+ 'bundler' => { required: true, available: defined?(Bundler), purpose: 'Dependency management' },
32
+ 'rack' => { required: true, available: defined?(Rack), purpose: 'Web server interface' },
33
+ 'activerecord' => { required: true, available: defined?(ActiveRecord), purpose: 'Database ORM' },
34
+ 'actionpack' => { required: true, available: defined?(ActionPack), purpose: 'Web framework core' },
35
+ 'activesupport' => { required: true, available: defined?(ActiveSupport), purpose: 'Core extensions' }
36
+ }
37
+
38
+ required.each do |name, info|
39
+ info[:status] = info[:available] ? 'loaded' : 'missing'
40
+ info[:version] = get_gem_version(name) if info[:available]
41
+ end
42
+
43
+ required
44
+ end
45
+
46
+ def check_optional_libraries
47
+ optional = {
48
+ 'sidekiq' => { available: defined?(Sidekiq), purpose: 'Background job processing' },
49
+ 'resque' => { available: defined?(Resque), purpose: 'Background job processing' },
50
+ 'redis' => { available: defined?(Redis), purpose: 'In-memory data store' },
51
+ 'puma' => { available: defined?(Puma), purpose: 'Web server' },
52
+ 'unicorn' => { available: defined?(Unicorn), purpose: 'Web server' },
53
+ 'passenger' => { available: defined?(PhusionPassenger), purpose: 'Web server' },
54
+ 'devise' => { available: defined?(Devise), purpose: 'Authentication' },
55
+ 'cancancan' => { available: defined?(CanCan), purpose: 'Authorization' },
56
+ 'turbo-rails' => { available: defined?(Turbo), purpose: 'SPA-like experience' },
57
+ 'stimulus-rails' => { available: defined?(Stimulus), purpose: 'JavaScript framework' },
58
+ 'bootsnap' => { available: defined?(Bootsnap), purpose: 'Boot optimization' },
59
+ 'sprockets' => { available: defined?(Sprockets), purpose: 'Asset pipeline' }
60
+ }
61
+
62
+ optional.each do |name, info|
63
+ info[:status] = info[:available] ? 'loaded' : 'not_loaded'
64
+ info[:version] = get_gem_version(name) if info[:available]
65
+ end
66
+
67
+ optional
68
+ end
69
+
70
+ def check_server_requirements
71
+ {
72
+ web_server: detect_web_server,
73
+ database: detect_database_adapter,
74
+ cache_store: detect_cache_store,
75
+ session_store: detect_session_store,
76
+ asset_host: (Rails.application.config.asset_host rescue nil)
77
+ }
78
+ end
79
+
80
+ def check_action_cable
81
+ return { available: false } unless defined?(ActionCable)
82
+
83
+ {
84
+ available: true,
85
+ adapter: (ActionCable.server.config.cable[:adapter] rescue 'unknown'),
86
+ url: (ActionCable.server.config.cable[:url] rescue nil),
87
+ allowed_request_origins: (ActionCable.server.config.allowed_request_origins rescue []),
88
+ status: action_cable_status
89
+ }
90
+ end
91
+
92
+ def environment_details
93
+ {
94
+ ruby_version: RUBY_VERSION,
95
+ ruby_platform: RUBY_PLATFORM,
96
+ rails_env: Rails.env,
97
+ rack_env: ENV['RACK_ENV'],
98
+ database_url: ENV['DATABASE_URL'] ? 'configured' : 'not_set',
99
+ redis_url: ENV['REDIS_URL'] ? 'configured' : 'not_set',
100
+ secret_key_base: ENV['SECRET_KEY_BASE'] ? 'configured' : 'not_set'
101
+ }
102
+ end
103
+
104
+ def get_gem_version(gem_name)
105
+ Gem.loaded_specs[gem_name]&.version&.to_s || 'unknown'
106
+ rescue
107
+ 'unknown'
108
+ end
109
+
110
+ def detect_web_server
111
+ return 'puma' if defined?(Puma)
112
+ return 'unicorn' if defined?(Unicorn)
113
+ return 'passenger' if defined?(PhusionPassenger)
114
+ return 'thin' if defined?(Thin)
115
+ return 'webrick' if defined?(WEBrick)
116
+ 'unknown'
117
+ end
118
+
119
+ def detect_database_adapter
120
+ ActiveRecord::Base.connection.adapter_name
121
+ rescue
122
+ 'unknown'
123
+ end
124
+
125
+ def detect_cache_store
126
+ cache_class = Rails.cache.class.name
127
+ {
128
+ type: cache_class,
129
+ explanation: get_cache_explanation(cache_class),
130
+ recommendation: get_cache_recommendation(cache_class)
131
+ }
132
+ rescue
133
+ {
134
+ type: 'unknown',
135
+ explanation: 'Unable to detect cache store',
136
+ recommendation: 'Check Rails cache configuration'
137
+ }
138
+ end
139
+
140
+ def detect_session_store
141
+ Rails.application.config.session_store.name
142
+ rescue
143
+ 'unknown'
144
+ end
145
+
146
+ def action_cable_status
147
+ return 'healthy' if ActionCable.server.config.cable[:adapter] != 'test'
148
+ 'not_configured'
149
+ rescue
150
+ 'error'
151
+ end
152
+
153
+ def get_cache_explanation(cache_class)
154
+ explanations = {
155
+ 'ActiveSupport::Cache::NullStore' => 'No caching (common in development)',
156
+ 'ActiveSupport::Cache::MemoryStore' => 'In-memory caching (single process)',
157
+ 'ActiveSupport::Cache::FileStore' => 'File-based caching (disk storage)',
158
+ 'ActiveSupport::Cache::RedisStore' => 'Redis-based caching (recommended for production)',
159
+ 'ActiveSupport::Cache::MemCacheStore' => 'Memcached-based caching (production ready)',
160
+ 'ActiveSupport::Cache::RedisCacheStore' => 'Redis caching with Rails 5.2+ features'
161
+ }
162
+ explanations[cache_class] || 'Custom or unknown cache store'
163
+ end
164
+
165
+ def get_cache_recommendation(cache_class)
166
+ case cache_class
167
+ when 'ActiveSupport::Cache::NullStore'
168
+ Rails.env.production? ? 'Configure Redis or Memcached for production' : 'OK for development'
169
+ when 'ActiveSupport::Cache::MemoryStore'
170
+ 'Consider Redis/Memcached for multi-server deployments'
171
+ when 'ActiveSupport::Cache::FileStore'
172
+ 'Consider Redis/Memcached for better performance'
173
+ when 'ActiveSupport::Cache::RedisStore', 'ActiveSupport::Cache::RedisCacheStore'
174
+ 'Excellent choice for production'
175
+ when 'ActiveSupport::Cache::MemCacheStore'
176
+ 'Good choice for production'
177
+ else
178
+ 'Verify cache store configuration'
179
+ end
180
+ end
181
+ end
182
+ end