rails-active-mcp 0.1.7 → 2.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +106 -279
  3. data/changelog.md +69 -0
  4. data/docs/DEBUGGING.md +5 -5
  5. data/docs/README.md +130 -142
  6. data/examples/rails_app_integration.md +405 -0
  7. data/exe/rails-active-mcp-server +153 -76
  8. data/gemfiles/rails_7.1.gemfile +34 -0
  9. data/lib/generators/rails_active_mcp/install/install_generator.rb +19 -39
  10. data/lib/generators/rails_active_mcp/install/templates/README.md +134 -188
  11. data/lib/generators/rails_active_mcp/install/templates/initializer.rb +65 -28
  12. data/lib/generators/rails_active_mcp/install/templates/mcp.ru +7 -3
  13. data/lib/rails_active_mcp/configuration.rb +37 -98
  14. data/lib/rails_active_mcp/console_executor.rb +13 -3
  15. data/lib/rails_active_mcp/engine.rb +36 -24
  16. data/lib/rails_active_mcp/sdk/server.rb +183 -0
  17. data/lib/rails_active_mcp/sdk/tools/console_execute_tool.rb +103 -0
  18. data/lib/rails_active_mcp/sdk/tools/dry_run_tool.rb +73 -0
  19. data/lib/rails_active_mcp/sdk/tools/model_info_tool.rb +106 -0
  20. data/lib/rails_active_mcp/sdk/tools/safe_query_tool.rb +77 -0
  21. data/lib/rails_active_mcp/tasks.rake +236 -80
  22. data/lib/rails_active_mcp/version.rb +1 -1
  23. data/lib/rails_active_mcp.rb +5 -11
  24. data/rails_active_mcp.gemspec +62 -11
  25. metadata +83 -24
  26. data/app/controllers/rails_active_mcp/mcp_controller.rb +0 -80
  27. data/lib/rails_active_mcp/mcp_server.rb +0 -383
  28. data/lib/rails_active_mcp/railtie.rb +0 -70
  29. data/lib/rails_active_mcp/stdio_server.rb +0 -517
  30. data/lib/rails_active_mcp/tools/console_execute_tool.rb +0 -61
  31. data/lib/rails_active_mcp/tools/dry_run_tool.rb +0 -41
  32. data/lib/rails_active_mcp/tools/model_info_tool.rb +0 -70
  33. data/lib/rails_active_mcp/tools/safe_query_tool.rb +0 -41
@@ -1,517 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
- require 'io/console'
5
- require 'logger'
6
-
7
- module RailsActiveMcp
8
- class StdioServer
9
- JSONRPC_VERSION = '2.0'
10
- MCP_VERSION = '2025-06-18'
11
-
12
- def initialize
13
- @tools = {}
14
- @mcp_server = McpServer.new
15
- @running = false
16
-
17
- # Ensure all logging goes to stderr, never stdout
18
- setup_logging
19
- register_default_tools
20
- log_to_stderr "Rails Active MCP Server initialized with #{@tools.size} tools", level: :info
21
- end
22
-
23
- def start
24
- @running = true
25
- log_to_stderr 'Rails Active MCP Server started successfully', level: :info
26
-
27
- # Send initial notification to stderr first, then stdout for MCP
28
- send_notification('info', 'Rails Active MCP Server started successfully')
29
-
30
- process_requests
31
- end
32
-
33
- def stop
34
- @running = false
35
- log_to_stderr 'Rails Active MCP Server stopped', level: :info
36
- end
37
-
38
- private
39
-
40
- def setup_logging
41
- # Configure Rails Active MCP logger to use stderr
42
- return unless RailsActiveMcp.respond_to?(:logger=)
43
-
44
- stderr_logger = Logger.new($stderr)
45
- stderr_logger.level = ENV['RAILS_MCP_DEBUG'] == '1' ? Logger::DEBUG : Logger::INFO
46
- stderr_logger.formatter = proc do |severity, datetime, progname, msg|
47
- "[#{datetime.strftime('%Y-%m-%d %H:%M:%S %z')}] [RAILS-MCP] #{severity}: #{msg}\n"
48
- end
49
- RailsActiveMcp.logger = stderr_logger
50
- end
51
-
52
- def process_requests
53
- while @running
54
- begin
55
- line = $stdin.gets
56
- break if line.nil?
57
-
58
- line = line.strip
59
- next if line.empty?
60
-
61
- log_to_stderr "Received request: #{line}", level: :debug
62
-
63
- request = JSON.parse(line)
64
- log_to_stderr "Processing method: #{request['method']}", level: :debug
65
-
66
- response = @mcp_server.handle_jsonrpc_request(request)
67
-
68
- log_to_stderr "Sending response: #{response.to_json}", level: :debug
69
-
70
- # Only JSON responses go to stdout
71
- $stdout.puts response.to_json
72
- $stdout.flush
73
- rescue JSON::ParserError => e
74
- log_to_stderr "JSON parse error: #{e.message}", level: :error
75
- send_error_response(nil, -32_700, 'Parse error')
76
- rescue StandardError => e
77
- log_to_stderr "Server error: #{e.message}", level: :error
78
- log_to_stderr e.backtrace.join("\n"), level: :debug
79
- send_error_response(nil, -32_603, 'Internal error')
80
- end
81
- end
82
- end
83
-
84
- def send_notification(level, message)
85
- notification = {
86
- jsonrpc: JSONRPC_VERSION,
87
- method: 'notifications/message',
88
- params: {
89
- level: level,
90
- data: message
91
- }
92
- }
93
-
94
- # Notifications go to stdout for MCP protocol
95
- $stdout.puts notification.to_json
96
- $stdout.flush
97
- end
98
-
99
- def send_error_response(id, code, message)
100
- error_response = {
101
- jsonrpc: JSONRPC_VERSION,
102
- id: id,
103
- error: {
104
- code: code,
105
- message: message
106
- }
107
- }
108
-
109
- $stdout.puts error_response.to_json
110
- $stdout.flush
111
- end
112
-
113
- def log_to_stderr(message, level: :info)
114
- return unless ENV['RAILS_MCP_DEBUG'] == '1' || level == :error || level == :info
115
-
116
- timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S %z')
117
- warn "[#{timestamp}] [RAILS-MCP] #{level.to_s.upcase}: #{message}"
118
- $stderr.flush
119
- end
120
-
121
- def handle_jsonrpc_request(data)
122
- case data['method']
123
- when 'initialize'
124
- handle_initialize(data)
125
- when 'tools/list'
126
- handle_tools_list(data)
127
- when 'tools/call'
128
- handle_tools_call(data)
129
- when 'resources/list'
130
- handle_resources_list(data)
131
- when 'resources/read'
132
- handle_resources_read(data)
133
- when 'ping'
134
- handle_ping(data)
135
- else
136
- jsonrpc_error(data['id'], -32_601, 'Method not found')
137
- end
138
- end
139
-
140
- def handle_initialize(data)
141
- {
142
- jsonrpc: JSONRPC_VERSION,
143
- id: data['id'],
144
- result: {
145
- protocolVersion: MCP_VERSION,
146
- capabilities: {
147
- tools: {},
148
- resources: {}
149
- },
150
- serverInfo: {
151
- name: 'rails-active-mcp',
152
- version: RailsActiveMcp::VERSION
153
- }
154
- }
155
- }
156
- end
157
-
158
- def handle_tools_list(data)
159
- tools_array = @tools.values.map do |tool|
160
- tool_def = {
161
- name: tool[:name],
162
- description: tool[:description],
163
- inputSchema: tool[:input_schema]
164
- }
165
-
166
- # Add annotations if present
167
- tool_def[:annotations] = tool[:annotations] if tool[:annotations] && !tool[:annotations].empty?
168
-
169
- tool_def
170
- end
171
-
172
- {
173
- jsonrpc: JSONRPC_VERSION,
174
- id: data['id'],
175
- result: { tools: tools_array }
176
- }
177
- end
178
-
179
- def handle_tools_call(data)
180
- tool_name = data.dig('params', 'name')
181
- arguments = data.dig('params', 'arguments') || {}
182
-
183
- tool = @tools[tool_name]
184
- return jsonrpc_error(data['id'], -32_602, "Tool '#{tool_name}' not found") unless tool
185
-
186
- log_to_stderr "Executing tool: #{tool_name}", level: :info
187
- send_notification('info', "Executing tool: #{tool_name}")
188
-
189
- begin
190
- start_time = Time.now
191
- result = tool[:handler].call(arguments)
192
- execution_time = Time.now - start_time
193
-
194
- log_to_stderr "Tool #{tool_name} completed in #{execution_time}s", level: :info
195
- send_notification('info', "Tool #{tool_name} completed successfully")
196
-
197
- {
198
- jsonrpc: JSONRPC_VERSION,
199
- id: data['id'],
200
- result: {
201
- content: [{ type: 'text', text: result.to_s }],
202
- isError: false
203
- }
204
- }
205
- rescue StandardError => e
206
- log_to_stderr "Tool execution error: #{e.message}", level: :error
207
- log_to_stderr e.backtrace.first(5).join("\n"), level: :debug if ENV['RAILS_MCP_DEBUG']
208
- send_notification('error', "Tool #{tool_name} failed: #{e.message}")
209
-
210
- {
211
- jsonrpc: JSONRPC_VERSION,
212
- id: data['id'],
213
- result: {
214
- content: [{ type: 'text', text: "Error: #{e.message}" }],
215
- isError: true
216
- }
217
- }
218
- end
219
- end
220
-
221
- def handle_resources_list(data)
222
- {
223
- jsonrpc: JSONRPC_VERSION,
224
- id: data['id'],
225
- result: { resources: [] }
226
- }
227
- end
228
-
229
- def handle_resources_read(data)
230
- {
231
- jsonrpc: JSONRPC_VERSION,
232
- id: data['id'],
233
- result: { contents: [] }
234
- }
235
- end
236
-
237
- def handle_ping(data)
238
- {
239
- jsonrpc: JSONRPC_VERSION,
240
- id: data['id'],
241
- result: {}
242
- }
243
- end
244
-
245
- def register_tool(name, description, input_schema, annotations = {}, &handler)
246
- @tools[name] = {
247
- name: name,
248
- description: description,
249
- input_schema: input_schema,
250
- annotations: annotations,
251
- handler: handler
252
- }
253
- end
254
-
255
- def register_default_tools
256
- register_tool(
257
- 'rails_console_execute',
258
- 'Execute Ruby code in Rails console context',
259
- {
260
- type: 'object',
261
- properties: {
262
- code: { type: 'string', description: 'Ruby code to execute' },
263
- timeout: { type: 'number', description: 'Timeout in seconds', default: 30 },
264
- safe_mode: { type: 'boolean', description: 'Enable safety checks', default: true },
265
- capture_output: { type: 'boolean', description: 'Capture console output', default: true }
266
- },
267
- required: ['code']
268
- },
269
- {
270
- title: 'Rails Console Executor',
271
- readOnlyHint: false,
272
- destructiveHint: true,
273
- idempotentHint: false,
274
- openWorldHint: false
275
- }
276
- ) do |args|
277
- execute_console_code(args)
278
- end
279
-
280
- register_tool(
281
- 'rails_model_info',
282
- 'Get information about Rails models including columns, associations, and table structure',
283
- {
284
- type: 'object',
285
- properties: {
286
- model_name: { type: 'string', description: 'Name of the Rails model class to inspect' }
287
- },
288
- required: ['model_name']
289
- },
290
- {
291
- title: 'Rails Model Inspector',
292
- readOnlyHint: true,
293
- destructiveHint: false,
294
- idempotentHint: true,
295
- openWorldHint: false
296
- }
297
- ) do |args|
298
- get_model_info(args['model_name'])
299
- end
300
-
301
- register_tool(
302
- 'rails_safe_query',
303
- 'Execute safe read-only database queries using ActiveRecord',
304
- {
305
- type: 'object',
306
- properties: {
307
- query: { type: 'string', description: 'ActiveRecord query to execute (read-only methods only)' },
308
- model: { type: 'string', description: 'Model class name to query against' }
309
- },
310
- required: %w[query model]
311
- },
312
- {
313
- title: 'Rails Safe Query Executor',
314
- readOnlyHint: true,
315
- destructiveHint: false,
316
- idempotentHint: true,
317
- openWorldHint: false
318
- }
319
- ) do |args|
320
- execute_safe_query(args)
321
- end
322
-
323
- register_tool(
324
- 'rails_dry_run',
325
- 'Analyze Ruby code for safety without executing it',
326
- {
327
- type: 'object',
328
- properties: {
329
- code: { type: 'string', description: 'Ruby code to analyze for safety and potential issues' }
330
- },
331
- required: ['code']
332
- },
333
- {
334
- title: 'Rails Code Safety Analyzer',
335
- readOnlyHint: true,
336
- destructiveHint: false,
337
- idempotentHint: true,
338
- openWorldHint: false
339
- }
340
- ) do |args|
341
- dry_run_analysis(args['code'])
342
- end
343
- end
344
-
345
- # Tool implementation methods (reused from McpServer)
346
- def execute_console_code(args)
347
- unless defined?(RailsActiveMcp) && RailsActiveMcp.respond_to?(:config) && RailsActiveMcp.config.enabled
348
- return 'Rails Active MCP is disabled. Enable it in your Rails configuration.'
349
- end
350
-
351
- executor = RailsActiveMcp::ConsoleExecutor.new(RailsActiveMcp.config)
352
-
353
- begin
354
- result = executor.execute(
355
- args['code'],
356
- timeout: args['timeout'] || 30,
357
- safe_mode: args['safe_mode'] != false,
358
- capture_output: args['capture_output'] != false
359
- )
360
-
361
- if result[:success]
362
- format_success_result(result)
363
- else
364
- "Error: #{result[:error]} (#{result[:error_class]})"
365
- end
366
- rescue RailsActiveMcp::SafetyError => e
367
- "Safety check failed: #{e.message}"
368
- rescue RailsActiveMcp::TimeoutError => e
369
- "Execution timed out: #{e.message}"
370
- rescue StandardError => e
371
- "Execution failed: #{e.message}"
372
- end
373
- end
374
-
375
- def get_model_info(model_name)
376
- unless defined?(RailsActiveMcp) && RailsActiveMcp.respond_to?(:config) && RailsActiveMcp.config.enabled
377
- return 'Rails Active MCP is disabled. Enable it in your Rails configuration.'
378
- end
379
-
380
- begin
381
- # Check if Rails environment is available
382
- unless defined?(Rails)
383
- return 'Rails environment not loaded. Please ensure the MCP server is started from your Rails app directory.'
384
- end
385
-
386
- model_class = model_name.constantize
387
- unless defined?(ActiveRecord) && model_class < ActiveRecord::Base
388
- return "#{model_name} is not an ActiveRecord model"
389
- end
390
-
391
- info = []
392
- info << "Model: #{model_class.name}"
393
- info << "Table: #{model_class.table_name}"
394
- info << "Columns: #{model_class.column_names.join(', ')}"
395
-
396
- associations = model_class.reflect_on_all_associations.map(&:name)
397
- info << "Associations: #{associations.any? ? associations.join(', ') : 'None'}"
398
-
399
- # Add validation info if available
400
- if model_class.respond_to?(:validators) && model_class.validators.any?
401
- validations = model_class.validators.map { |v| "#{v.attributes.join(', ')}: #{v.class.name.demodulize}" }.uniq
402
- info << "Validations: #{validations.join(', ')}"
403
- end
404
-
405
- info.join("\n")
406
- rescue NameError
407
- "Model '#{model_name}' not found. Make sure the model class exists and is properly defined."
408
- rescue StandardError => e
409
- "Error getting model info: #{e.message}"
410
- end
411
- end
412
-
413
- def execute_safe_query(args)
414
- unless defined?(RailsActiveMcp) && RailsActiveMcp.respond_to?(:config) && RailsActiveMcp.config.enabled
415
- return 'Rails Active MCP is disabled. Enable it in your Rails configuration.'
416
- end
417
-
418
- begin
419
- # Check if Rails environment is available
420
- unless defined?(Rails)
421
- return 'Rails environment not loaded. Please ensure the MCP server is started from your Rails app directory.'
422
- end
423
-
424
- model_class = args['model'].constantize
425
- unless defined?(ActiveRecord) && model_class < ActiveRecord::Base
426
- return "#{args['model']} is not an ActiveRecord model"
427
- end
428
-
429
- # Only allow safe read-only methods
430
- safe_methods = %w[find find_by where select count sum average maximum minimum first last pluck ids exists?
431
- empty? any? many? include? limit offset order group having joins includes references distinct uniq readonly]
432
-
433
- # Extract the first method call to validate it's safe
434
- query_parts = args['query'].split('.')
435
- query_method = query_parts.first.split('(').first
436
-
437
- unless safe_methods.include?(query_method)
438
- return "Unsafe query method: #{query_method}. Only read-only methods are allowed."
439
- end
440
-
441
- result = model_class.instance_eval(args['query'])
442
-
443
- # Format result appropriately
444
- case result
445
- when ActiveRecord::Relation
446
- "Query returned #{result.count} records: #{result.limit(10).pluck(:id).join(', ')}#{result.count > 10 ? '...' : ''}"
447
- when Array
448
- "Array with #{result.length} items: #{result.take(5).inspect}#{result.length > 5 ? '...' : ''}"
449
- else
450
- result.to_s
451
- end
452
- rescue NameError
453
- "Model '#{args['model']}' not found. Make sure the model class exists and is properly defined."
454
- rescue StandardError => e
455
- "Error executing query: #{e.message}"
456
- end
457
- end
458
-
459
- def dry_run_analysis(code)
460
- unless defined?(RailsActiveMcp) && RailsActiveMcp.respond_to?(:config) && RailsActiveMcp.config.enabled
461
- return 'Rails Active MCP is disabled. Enable it in your Rails configuration.'
462
- end
463
-
464
- executor = RailsActiveMcp::ConsoleExecutor.new(RailsActiveMcp.config)
465
-
466
- begin
467
- analysis = executor.dry_run(code)
468
-
469
- output = []
470
- output << 'Code Analysis Results:'
471
- output << "Code: #{analysis[:code]}"
472
- output << "Safe: #{analysis[:safety_analysis][:safe] ? 'Yes' : 'No'}"
473
- output << "Read-only: #{analysis[:safety_analysis][:read_only] ? 'Yes' : 'No'}"
474
- output << "Risk level: #{analysis[:estimated_risk]}"
475
- output << "Would execute: #{analysis[:would_execute] ? 'Yes' : 'No'}"
476
- output << "Summary: #{analysis[:safety_analysis][:summary]}"
477
-
478
- if analysis[:safety_analysis][:violations] && analysis[:safety_analysis][:violations].any?
479
- output << "\nSafety Violations:"
480
- analysis[:safety_analysis][:violations].each do |violation|
481
- output << " - #{violation[:description]} (#{violation[:severity]})"
482
- end
483
- end
484
-
485
- if analysis[:recommendations] && analysis[:recommendations].any?
486
- output << "\nRecommendations:"
487
- analysis[:recommendations].each do |rec|
488
- output << " - #{rec}"
489
- end
490
- end
491
-
492
- output.join("\n")
493
- rescue StandardError => e
494
- "Analysis failed: #{e.message}. Make sure the Rails environment is properly loaded."
495
- end
496
- end
497
-
498
- def format_success_result(result)
499
- output = []
500
- output << 'Execution Results:'
501
- output << "Code: #{result[:code]}"
502
- output << "Result: #{result[:return_value_string] || result[:return_value]}"
503
- output << "Output: #{result[:output]}" if result[:output] && !result[:output].empty?
504
- output << "Execution time: #{result[:execution_time]}s" if result[:execution_time]
505
- output << "Note: #{result[:note]}" if result[:note]
506
- output.join("\n")
507
- end
508
-
509
- def jsonrpc_error(id, code, message)
510
- {
511
- jsonrpc: JSONRPC_VERSION,
512
- id: id,
513
- error: { code: code, message: message }
514
- }
515
- end
516
- end
517
- end
@@ -1,61 +0,0 @@
1
- module RailsActiveMcp
2
- module Tools
3
- class ConsoleExecuteTool < ApplicationMCPTool
4
- tool_name "console_execute"
5
- description "Execute Ruby code in Rails console with safety checks"
6
-
7
- property :code, type: "string", description: 'Ruby code to execute in Rails console', required: true
8
- property :safe_mode, type: "boolean", description: 'Enable safety checks (default: true)', required: false
9
- property :timeout, type: "integer", description: 'Timeout in seconds (default: 30)', required: false
10
- property :capture_output, type: "boolean", description: 'Capture console output (default: true)', required: false
11
-
12
- def perform
13
- code = properties[:code]
14
- safe_mode = properties[:safe_mode]
15
- timeout = properties[:timeout]
16
- capture_output = properties.fetch(:capture_output, true)
17
-
18
- return render(error: "Rails Active MCP is disabled") unless RailsActiveMcp.config.enabled
19
-
20
- executor = RailsActiveMcp::ConsoleExecutor.new(RailsActiveMcp.config)
21
-
22
- begin
23
- result = executor.execute(
24
- code,
25
- timeout: timeout,
26
- safe_mode: safe_mode,
27
- capture_output: capture_output
28
- )
29
-
30
- if result[:success]
31
- render(text: format_success_result(result))
32
- else
33
- render(error: [format_error_result(result)])
34
- end
35
- rescue RailsActiveMcp::SafetyError => e
36
- render(error: ["Safety check failed: #{e.message}"])
37
- rescue RailsActiveMcp::TimeoutError => e
38
- render(error: ["Execution timed out: #{e.message}"])
39
- rescue => e
40
- render(error: ["Execution failed: #{e.message}"])
41
- end
42
- end
43
-
44
- private
45
-
46
- def format_success_result(result)
47
- output = []
48
- output << "Code: #{result[:code]}"
49
- output << "Result: #{result[:return_value_string] || result[:return_value]}"
50
- output << "Output: #{result[:output]}" if result[:output].present?
51
- output << "Execution time: #{result[:execution_time]}s" if result[:execution_time]
52
- output << "Note: #{result[:note]}" if result[:note]
53
- output.join("\n")
54
- end
55
-
56
- def format_error_result(result)
57
- "Error: #{result[:error]} (#{result[:error_class]})"
58
- end
59
- end
60
- end
61
- end
@@ -1,41 +0,0 @@
1
- module RailsActiveMcp
2
- module Tools
3
- class DryRunTool < ApplicationMCPTool
4
- tool_name "dry_run"
5
- description "Analyze Ruby code for safety without executing it"
6
-
7
- property :code, type: "string", description: 'Ruby code to analyze for safety', required: true
8
-
9
- def perform
10
- return render(error: "Rails Active MCP is disabled") unless RailsActiveMcp.config.enabled
11
-
12
- code = properties[:code]
13
- executor = RailsActiveMcp::ConsoleExecutor.new(RailsActiveMcp.config)
14
- analysis = executor.dry_run(code)
15
-
16
- output = []
17
- output << "Code: #{analysis[:code]}"
18
- output << "Safe: #{analysis[:safety_analysis][:safe] ? 'Yes' : 'No'}"
19
- output << "Read-only: #{analysis[:safety_analysis][:read_only] ? 'Yes' : 'No'}"
20
- output << "Risk level: #{analysis[:estimated_risk]}"
21
- output << "Summary: #{analysis[:safety_analysis][:summary]}"
22
-
23
- if analysis[:safety_analysis][:violations].any?
24
- output << "\nViolations:"
25
- analysis[:safety_analysis][:violations].each do |violation|
26
- output << " - #{violation[:description]} (#{violation[:severity]})"
27
- end
28
- end
29
-
30
- if analysis[:recommendations].any?
31
- output << "\nRecommendations:"
32
- analysis[:recommendations].each do |rec|
33
- output << " - #{rec}"
34
- end
35
- end
36
-
37
- render(text: output.join("\n"))
38
- end
39
- end
40
- end
41
- end
@@ -1,70 +0,0 @@
1
- module RailsActiveMcp
2
- module Tools
3
- class ModelInfoTool < ApplicationMCPTool
4
- tool_name "model_info"
5
- description "Get information about Rails models including schema and associations"
6
-
7
- property :model, type: "string", description: 'Model class name', required: true
8
- property :include_schema, type: "boolean", description: 'Include database schema information', required: false
9
- property :include_associations, type: "boolean", description: 'Include model associations', required: false
10
- property :include_validations, type: "boolean", description: 'Include model validations', required: false
11
-
12
- def perform
13
- return render(error: "Rails Active MCP is disabled") unless RailsActiveMcp.config.enabled
14
-
15
- model = properties[:model]
16
- include_schema = properties.fetch(:include_schema, true)
17
- include_associations = properties.fetch(:include_associations, true)
18
- include_validations = properties.fetch(:include_validations, true)
19
-
20
- begin
21
- model_class = model.constantize
22
-
23
- output = []
24
- output << "Model: #{model}"
25
- output << "Table: #{model_class.table_name}"
26
- output << "Primary Key: #{model_class.primary_key}"
27
-
28
- if include_schema
29
- output << "\nSchema:"
30
- model_class.columns.each do |column|
31
- output << " #{column.name}: #{column.type} (#{column.sql_type})"
32
- output << " - Null: #{column.null}"
33
- output << " - Default: #{column.default}" if column.default
34
- end
35
- end
36
-
37
- if include_associations
38
- output << "\nAssociations:"
39
- model_class.reflections.each do |name, reflection|
40
- output << " #{name}: #{reflection.class.name.split('::').last} -> #{reflection.class_name}"
41
- end
42
- end
43
-
44
- if include_validations
45
- validations = {}
46
- model_class.validators.each do |validator|
47
- validator.attributes.each do |attribute|
48
- validations[attribute] ||= []
49
- validations[attribute] << validator.class.name.split('::').last
50
- end
51
- end
52
-
53
- if validations.any?
54
- output << "\nValidations:"
55
- validations.each do |attr, validators|
56
- output << " #{attr}: #{validators.join(', ')}"
57
- end
58
- end
59
- end
60
-
61
- render(text: output.join("\n"))
62
- rescue NameError
63
- render(error: ["Model '#{model}' not found"])
64
- rescue => e
65
- render(error: ["Error analyzing model: #{e.message}"])
66
- end
67
- end
68
- end
69
- end
70
- end