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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +52 -0
- data/LICENSE +21 -0
- data/README.md +314 -0
- data/README_ja.md +314 -0
- data/README_zh.md +314 -0
- data/lib/rails_flow_map/analyzers/controller_analyzer.rb +124 -0
- data/lib/rails_flow_map/analyzers/model_analyzer.rb +139 -0
- data/lib/rails_flow_map/configuration.rb +22 -0
- data/lib/rails_flow_map/engine.rb +16 -0
- data/lib/rails_flow_map/errors.rb +240 -0
- data/lib/rails_flow_map/formatters/d3js_formatter.rb +488 -0
- data/lib/rails_flow_map/formatters/erd_formatter.rb +64 -0
- data/lib/rails_flow_map/formatters/git_diff_formatter.rb +589 -0
- data/lib/rails_flow_map/formatters/graphviz_formatter.rb +111 -0
- data/lib/rails_flow_map/formatters/mermaid_formatter.rb +91 -0
- data/lib/rails_flow_map/formatters/metrics_formatter.rb +196 -0
- data/lib/rails_flow_map/formatters/openapi_formatter.rb +557 -0
- data/lib/rails_flow_map/formatters/plantuml_formatter.rb +92 -0
- data/lib/rails_flow_map/formatters/sequence_formatter.rb +288 -0
- data/lib/rails_flow_map/generators/install/templates/rails_flow_map.rb +34 -0
- data/lib/rails_flow_map/generators/install_generator.rb +32 -0
- data/lib/rails_flow_map/logging.rb +215 -0
- data/lib/rails_flow_map/models/flow_edge.rb +31 -0
- data/lib/rails_flow_map/models/flow_graph.rb +58 -0
- data/lib/rails_flow_map/models/flow_node.rb +37 -0
- data/lib/rails_flow_map/version.rb +3 -0
- data/lib/rails_flow_map.rb +310 -0
- data/lib/tasks/rails_flow_map.rake +70 -0
- metadata +156 -0
data/README_zh.md
ADDED
@@ -0,0 +1,314 @@
|
|
1
|
+
# Rails Flow Map 🚀
|
2
|
+
|
3
|
+
[](https://github.com/railsflowmap/rails-flow-map)
|
4
|
+
[](LICENSE)
|
5
|
+
[](https://www.ruby-lang.org/)
|
6
|
+
[](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
|