rails-flow-map 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.
data/README_zh.md ADDED
@@ -0,0 +1,314 @@
1
+ # Rails Flow Map 🚀
2
+
3
+ [![Version](https://img.shields.io/badge/version-1.0.0-blue.svg)](https://github.com/railsflowmap/rails-flow-map)
4
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
5
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%202.7.0-red.svg)](https://www.ruby-lang.org/)
6
+ [![Rails](https://img.shields.io/badge/rails-%3E%3D%206.0-red.svg)](https://rubyonrails.org/)
7
+
8
+ > 🎯 **全面的Rails应用架构可视化工具**
9
+
10
+ Rails Flow Map是一个强大的gem,它分析您的Rails应用程序结构并生成精美的交互式可视化,帮助理解架构、依赖关系和数据流模式。
11
+
12
+ **[English](README.md) | [日本語](README_ja.md)**
13
+
14
+ ---
15
+
16
+ ## ✨ 功能特性
17
+
18
+ ### 🎨 多种可视化格式
19
+ - **🌊 Mermaid图表** - GitHub友好的Markdown图表
20
+ - **🏗️ PlantUML** - 详细的UML类图
21
+ - **🔗 GraphViz** - 网络式关系图
22
+ - **⚡ 交互式D3.js** - 可缩放、可拖拽的Web可视化
23
+ - **📊 指标报告** - 代码质量和复杂性分析
24
+ - **🔄 序列图** - API端点流程可视化
25
+ - **📋 OpenAPI规范** - 自动生成API文档
26
+ - **📈 ERD图表** - 数据库模式可视化
27
+ - **🔍 Git差异视图** - 架构变更对比
28
+
29
+ ### 🛡️ 企业级安全
30
+ - **路径遍历保护** - 防止恶意文件访问
31
+ - **XSS防护** - 清理所有HTML输出
32
+ - **输入验证** - 全面的参数检查
33
+ - **安全事件日志** - 跟踪潜在威胁
34
+
35
+ ### ⚡ 性能与可靠性
36
+ - **结构化日志** - 性能指标和调试信息
37
+ - **错误处理** - 带上下文的强健异常管理
38
+ - **重试逻辑** - 从暂时性故障自动恢复
39
+ - **内存优化** - 大型应用的高效处理
40
+
41
+ ### 🔧 开发者体验
42
+ - **零配置** - 开箱即用
43
+ - **灵活集成** - Rake任务、Ruby API、CI/CD支持
44
+ - **全面文档** - 示例和最佳实践
45
+ - **VS Code集成** - 内置任务定义
46
+
47
+ ---
48
+
49
+ ## 🚀 快速开始
50
+
51
+ ### 安装
52
+
53
+ 添加到Gemfile:
54
+
55
+ ```ruby
56
+ gem 'rails-flow-map'
57
+ ```
58
+
59
+ ```bash
60
+ bundle install
61
+ rails generate rails_flow_map:install
62
+ ```
63
+
64
+ ### 基本用法
65
+
66
+ ```ruby
67
+ # 生成架构概览
68
+ graph = RailsFlowMap.analyze
69
+ RailsFlowMap.export(graph, format: :mermaid, output: 'docs/architecture.md')
70
+
71
+ # 创建交互式可视化
72
+ RailsFlowMap.export(graph, format: :d3js, output: 'public/architecture.html')
73
+
74
+ # 生成API文档
75
+ RailsFlowMap.export(graph, format: :openapi, output: 'docs/api.yaml')
76
+ ```
77
+
78
+ ### 使用Rake任务
79
+
80
+ ```bash
81
+ # 生成所有可视化
82
+ rake flow_map:generate
83
+
84
+ # 特定格式
85
+ rake flow_map:generate FORMAT=mermaid OUTPUT=docs/flow.md
86
+
87
+ # 分析API端点
88
+ rake flow_map:endpoint ENDPOINT=/api/v1/users FORMAT=sequence
89
+ ```
90
+
91
+ ---
92
+
93
+ ## 📊 可视化示例
94
+
95
+ ### 🌊 Mermaid架构图
96
+
97
+ ```mermaid
98
+ graph TD
99
+ User[User] --> Post[Post]
100
+ User --> Comment[Comment]
101
+ Post --> Comment
102
+ UsersController --> User
103
+ PostsController --> Post
104
+ API[/api/v1/users] --> UsersController
105
+ ```
106
+
107
+ ### ⚡ 交互式D3.js可视化
108
+
109
+ *功能:缩放、拖拽、按组件类型过滤、搜索功能*
110
+
111
+ ### 📋 OpenAPI文档
112
+
113
+ ```yaml
114
+ openapi: 3.0.0
115
+ info:
116
+ title: Rails API Documentation
117
+ version: 1.0.0
118
+ paths:
119
+ /api/v1/users:
120
+ get:
121
+ summary: 获取所有用户
122
+ responses:
123
+ 200:
124
+ description: 成功响应
125
+ ```
126
+
127
+ ---
128
+
129
+ ## 🎯 使用场景
130
+
131
+ ### 👥 开发团队
132
+
133
+ - **📚 文档化** - 自动生成始终最新的架构文档
134
+ - **🔍 代码审查** - 在PR中可视化架构变更
135
+ - **🎓 新人培训** - 帮助新团队成员理解代码库
136
+ - **🏗️ 重构** - 在更改前识别依赖关系
137
+
138
+ ### 🚀 DevOps与CI/CD
139
+
140
+ - **📊 监控** - 跟踪架构复杂性随时间的变化
141
+ - **🔄 自动化** - 在部署时自动生成文档
142
+ - **📈 指标** - 收集代码质量和依赖关系指标
143
+ - **🚨 警报** - 检测破坏性架构变更
144
+
145
+ ### 📋 API团队
146
+
147
+ - **📖 API文档** - 自动生成OpenAPI规范
148
+ - **🔄 流程图** - 可视化请求/响应流程
149
+ - **🧪 测试** - 理解端点依赖关系
150
+ - **📚 客户端SDK** - 提供清晰的API结构文档
151
+
152
+ ---
153
+
154
+ ## 🔧 配置
155
+
156
+ ### 基本配置
157
+
158
+ ```ruby
159
+ # config/initializers/rails_flow_map.rb
160
+ RailsFlowMap.configure do |config|
161
+ config.output_directory = 'doc/flow_maps'
162
+ config.exclude_paths = ['vendor/', 'tmp/']
163
+ config.default_format = :mermaid
164
+ end
165
+ ```
166
+
167
+ ### 高级配置
168
+
169
+ ```ruby
170
+ RailsFlowMap.configure do |config|
171
+ # 分析选项
172
+ config.include_models = true
173
+ config.include_controllers = true
174
+ config.include_routes = true
175
+
176
+ # 性能选项
177
+ config.streaming_mode = true
178
+ config.memory_limit = 512.megabytes
179
+
180
+ # 安全选项
181
+ config.sanitize_output = true
182
+ config.allow_system_paths = false
183
+ end
184
+ ```
185
+
186
+ ---
187
+
188
+ ## 📚 文档
189
+
190
+ ### 快速参考
191
+ - 📖 [**使用示例**](USAGE_EXAMPLES.md) - 全面使用指南
192
+ - ⚡ [**快速参考**](QUICK_REFERENCE.md) - 常用命令和模式
193
+ - 🔧 [**API文档**](https://rubydoc.info/github/railsflowmap/rails-flow-map) - YARD文档
194
+
195
+ ### 集成指南
196
+ - 🔄 [**CI/CD集成**](docs/ci_cd_integration.md) - GitHub Actions、GitLab CI
197
+ - 💻 [**VS Code集成**](doc/vscode_integration.md) - 编辑器设置和任务
198
+ - 🐳 [**Docker集成**](docs/docker_integration.md) - 容器化工作流
199
+
200
+ ### 示例
201
+ - 🚀 [**基础示例**](examples/basic_usage.rb) - 入门代码示例
202
+ - 🔬 [**高级模式**](examples/advanced_patterns.rb) - 复杂用例
203
+
204
+ ---
205
+
206
+ ## 🛠️ 支持的格式
207
+
208
+ | 格式 | 描述 | 最适用于 | 输出 |
209
+ |--------|-------------|----------|---------|
210
+ | `mermaid` | GitHub友好图表 | 文档、README | `.md` |
211
+ | `plantuml` | 详细UML图表 | 技术文档 | `.puml` |
212
+ | `d3js` | 交互式可视化 | 探索、演示 | `.html` |
213
+ | `openapi` | API规范 | API文档 | `.yaml` |
214
+ | `sequence` | 请求流程图 | API分析 | `.md` |
215
+ | `erd` | 数据库模式 | 数据建模 | `.md` |
216
+ | `metrics` | 代码质量报告 | 代码审查、监控 | `.md` |
217
+ | `graphviz` | 网络图 | 复杂关系 | `.dot` |
218
+
219
+ ---
220
+
221
+ ## 🔗 集成
222
+
223
+ ### GitHub Actions
224
+
225
+ ```yaml
226
+ name: 生成架构文档
227
+ on: [push]
228
+ jobs:
229
+ docs:
230
+ runs-on: ubuntu-latest
231
+ steps:
232
+ - uses: actions/checkout@v3
233
+ - uses: ruby/setup-ruby@v1
234
+ - run: bundle exec rake flow_map:generate_all
235
+ ```
236
+
237
+ ### VS Code任务
238
+
239
+ ```json
240
+ {
241
+ "label": "生成架构文档",
242
+ "type": "shell",
243
+ "command": "bundle exec rake flow_map:generate_all"
244
+ }
245
+ ```
246
+
247
+ ### Pre-commit钩子
248
+
249
+ ```bash
250
+ #!/bin/bash
251
+ bundle exec rake flow_map:diff > ARCHITECTURE_CHANGES.md
252
+ git add ARCHITECTURE_CHANGES.md
253
+ ```
254
+
255
+ ---
256
+
257
+ ## 🤝 贡献
258
+
259
+ 我们欢迎贡献!详情请参见[贡献指南](CONTRIBUTING.md)。
260
+
261
+ ### 开发环境设置
262
+
263
+ ```bash
264
+ git clone https://github.com/railsflowmap/rails-flow-map.git
265
+ cd rails-flow-map
266
+ bundle install
267
+ rake spec
268
+ ```
269
+
270
+ ### 运行测试
271
+
272
+ ```bash
273
+ # 运行所有测试
274
+ bundle exec rspec
275
+
276
+ # 运行特定测试
277
+ bundle exec rspec spec/rails_flow_map/formatters/mermaid_formatter_spec.rb
278
+
279
+ # 带覆盖率运行
280
+ COVERAGE=true bundle exec rspec
281
+ ```
282
+
283
+ ---
284
+
285
+ ## 📄 许可证
286
+
287
+ Rails Flow Map在[MIT许可证](LICENSE)下发布。
288
+
289
+ ---
290
+
291
+ ## 🙏 致谢
292
+
293
+ - 感谢所有[贡献者](https://github.com/railsflowmap/rails-flow-map/contributors)
294
+ - 受Rails社区对更好架构可视化需求的启发
295
+ - 用❤️为Rails生态系统构建
296
+
297
+ ---
298
+
299
+ ## 🔗 链接
300
+
301
+ - 📖 [文档](https://docs.railsflowmap.org)
302
+ - 🐛 [Bug报告](https://github.com/railsflowmap/rails-flow-map/issues)
303
+ - 💬 [讨论](https://github.com/railsflowmap/rails-flow-map/discussions)
304
+ - 🐦 [Twitter](https://twitter.com/railsflowmap)
305
+
306
+ ---
307
+
308
+ <div align="center">
309
+
310
+ **⭐ 如果Rails Flow Map对您的团队有帮助,请在GitHub上给我们星标! ⭐**
311
+
312
+ [⬆ 返回顶部](#rails-flow-map-)
313
+
314
+ </div>
@@ -0,0 +1,124 @@
1
+ require 'parser/current'
2
+
3
+ module RailsFlowMap
4
+ class ControllerAnalyzer
5
+ def initialize(graph)
6
+ @graph = graph
7
+ end
8
+
9
+ def analyze
10
+ controller_files.each do |file|
11
+ analyze_controller_file(file)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def controller_files
18
+ Dir.glob(Rails.root.join('app', 'controllers', '**', '*.rb'))
19
+ end
20
+
21
+ def analyze_controller_file(file)
22
+ content = File.read(file)
23
+ ast = Parser::CurrentRuby.parse(content)
24
+ return unless ast
25
+
26
+ ControllerVisitor.new(@graph, file).process(ast)
27
+ rescue Parser::SyntaxError => e
28
+ Rails.logger.warn "Failed to parse #{file}: #{e.message}"
29
+ end
30
+
31
+ class ControllerVisitor < Parser::AST::Processor
32
+ def initialize(graph, file_path)
33
+ @graph = graph
34
+ @file_path = file_path
35
+ @current_controller = nil
36
+ end
37
+
38
+ def on_class(node)
39
+ class_name_node, superclass_node, body_node = *node
40
+ class_name = extract_class_name(class_name_node)
41
+
42
+ if class_name && is_controller?(class_name, superclass_node)
43
+ @current_controller = class_name
44
+ node_id = "controller_#{class_name.underscore}"
45
+
46
+ controller_node = FlowNode.new(
47
+ id: node_id,
48
+ name: class_name,
49
+ type: :controller,
50
+ file_path: @file_path,
51
+ line_number: node.loc.line
52
+ )
53
+
54
+ @graph.add_node(controller_node)
55
+ end
56
+
57
+ super
58
+ ensure
59
+ @current_controller = nil
60
+ end
61
+
62
+ def on_def(node)
63
+ return unless @current_controller
64
+
65
+ method_name = node.children[0].to_s
66
+
67
+ if action_method?(method_name)
68
+ action_id = "action_#{@current_controller.underscore}_#{method_name}"
69
+ controller_id = "controller_#{@current_controller.underscore}"
70
+
71
+ action_node = FlowNode.new(
72
+ id: action_id,
73
+ name: method_name,
74
+ type: :action,
75
+ attributes: { controller: @current_controller },
76
+ file_path: @file_path,
77
+ line_number: node.loc.line
78
+ )
79
+
80
+ @graph.add_node(action_node)
81
+
82
+ edge = FlowEdge.new(
83
+ from: controller_id,
84
+ to: action_id,
85
+ type: :has_action
86
+ )
87
+
88
+ @graph.add_edge(edge)
89
+ end
90
+
91
+ super
92
+ end
93
+
94
+ private
95
+
96
+ def extract_class_name(node)
97
+ case node.type
98
+ when :const
99
+ node.children[1].to_s
100
+ when :module
101
+ const_node = node.children.first
102
+ const_node.children[1].to_s if const_node.type == :const
103
+ end
104
+ end
105
+
106
+ def is_controller?(class_name, superclass_node)
107
+ return false unless class_name.end_with?('Controller')
108
+ return true if superclass_node.nil?
109
+
110
+ if superclass_node.type == :const
111
+ const_name = superclass_node.children[1].to_s
112
+ ['ApplicationController', 'ActionController::Base', 'ActionController::API'].include?(const_name)
113
+ else
114
+ false
115
+ end
116
+ end
117
+
118
+ def action_method?(method_name)
119
+ !%w[initialize before_action after_action around_action].include?(method_name) &&
120
+ !method_name.start_with?('_')
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,139 @@
1
+ require 'parser/current'
2
+
3
+ module RailsFlowMap
4
+ class ModelAnalyzer
5
+ def initialize(graph)
6
+ @graph = graph
7
+ end
8
+
9
+ def analyze
10
+ model_files.each do |file|
11
+ analyze_model_file(file)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def model_files
18
+ Dir.glob(Rails.root.join('app', 'models', '**', '*.rb'))
19
+ end
20
+
21
+ def analyze_model_file(file)
22
+ content = File.read(file)
23
+ ast = Parser::CurrentRuby.parse(content)
24
+ return unless ast
25
+
26
+ ModelVisitor.new(@graph, file).process(ast)
27
+ rescue Parser::SyntaxError => e
28
+ Rails.logger.warn "Failed to parse #{file}: #{e.message}"
29
+ end
30
+
31
+ class ModelVisitor < Parser::AST::Processor
32
+ def initialize(graph, file_path)
33
+ @graph = graph
34
+ @file_path = file_path
35
+ @current_class = nil
36
+ end
37
+
38
+ def on_class(node)
39
+ class_name_node, superclass_node, body_node = *node
40
+ class_name = extract_class_name(class_name_node)
41
+
42
+ if class_name && is_active_record_model?(superclass_node)
43
+ @current_class = class_name
44
+ node_id = "model_#{class_name.downcase}"
45
+
46
+ model_node = FlowNode.new(
47
+ id: node_id,
48
+ name: class_name,
49
+ type: :model,
50
+ file_path: @file_path,
51
+ line_number: node.loc.line
52
+ )
53
+
54
+ @graph.add_node(model_node)
55
+ end
56
+
57
+ super
58
+ ensure
59
+ @current_class = nil
60
+ end
61
+
62
+ def on_send(node)
63
+ return unless @current_class
64
+
65
+ receiver, method_name, *args = *node
66
+
67
+ if receiver.nil? && association_method?(method_name)
68
+ process_association(method_name, args)
69
+ end
70
+
71
+ super
72
+ end
73
+
74
+ private
75
+
76
+ def extract_class_name(node)
77
+ case node.type
78
+ when :const
79
+ node.children[1].to_s
80
+ when :module
81
+ const_node = node.children.first
82
+ const_node.children[1].to_s if const_node.type == :const
83
+ end
84
+ end
85
+
86
+ def is_active_record_model?(superclass_node)
87
+ return true if superclass_node.nil?
88
+
89
+ if superclass_node.type == :const
90
+ const_name = superclass_node.children[1].to_s
91
+ ['ApplicationRecord', 'ActiveRecord::Base'].include?(const_name)
92
+ else
93
+ false
94
+ end
95
+ end
96
+
97
+ def association_method?(method_name)
98
+ [:belongs_to, :has_one, :has_many, :has_and_belongs_to_many, :has_many_through].include?(method_name)
99
+ end
100
+
101
+ def process_association(method_name, args)
102
+ return if args.empty?
103
+
104
+ association_name = extract_association_name(args.first)
105
+ return unless association_name
106
+
107
+ target_model = infer_model_name(association_name, method_name)
108
+ from_id = "model_#{@current_class.downcase}"
109
+ to_id = "model_#{target_model.downcase}"
110
+
111
+ edge = FlowEdge.new(
112
+ from: from_id,
113
+ to: to_id,
114
+ type: method_name,
115
+ label: association_name.to_s
116
+ )
117
+
118
+ @graph.add_edge(edge)
119
+ end
120
+
121
+ def extract_association_name(node)
122
+ case node.type
123
+ when :sym
124
+ node.children.first
125
+ when :str
126
+ node.children.first.to_sym
127
+ end
128
+ end
129
+
130
+ def infer_model_name(association_name, method_name)
131
+ if [:has_many, :has_and_belongs_to_many].include?(method_name)
132
+ association_name.to_s.singularize.camelize
133
+ else
134
+ association_name.to_s.camelize
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,22 @@
1
+ module RailsFlowMap
2
+ class Configuration
3
+ attr_accessor :exclude_paths, :model_pattern, :controller_pattern,
4
+ :default_format, :output_directory, :include_sti,
5
+ :include_polymorphic, :node_colors
6
+
7
+ def initialize
8
+ @exclude_paths = []
9
+ @model_pattern = 'app/models/**/*.rb'
10
+ @controller_pattern = 'app/controllers/**/*.rb'
11
+ @default_format = :mermaid
12
+ @output_directory = 'doc/flow_maps'
13
+ @include_sti = true
14
+ @include_polymorphic = true
15
+ @node_colors = {
16
+ model: '#f9f',
17
+ controller: '#bbf',
18
+ action: '#bfb'
19
+ }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ module RailsFlowMap
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace RailsFlowMap
4
+
5
+ initializer "rails_flow_map.load_tasks" do
6
+ if defined?(Rake)
7
+ rake_file = File.expand_path("../../tasks/rails_flow_map.rake", __dir__)
8
+ load rake_file if File.exist?(rake_file)
9
+ end
10
+ end
11
+
12
+ generators do
13
+ require "rails_flow_map/generators/install_generator"
14
+ end
15
+ end
16
+ end