上下文:我正在解决一个问题,我需要一个外部审计程序才能理解和“应用”Rails路由。编写这个外部程序的一个选择可能是解析rake routes
的输出,但这会不必要地最终复制解析这些路由并将它们转换为结构良好的Journey::Route
对象的代码。
因此,我的计划是将Rails.application.routes
输出为外部程序可以理解的通用格式(YAML或JSON),并且可以基于此数据构建路由器。
问题:鉴于此背景,我试图理解Journey::Path::Paternet#spec
属性的结构,该属性发生在Journey::Route
对象内部,恰好是所有动作的中心。
例如,以下路线 - /posts/:id
- 转换为以下“规范” -
#<Journey::Nodes::Cat:0x00007ff193327ee0
@left=
#<Journey::Nodes::Cat:0x00007ff193308630
@left=
#<Journey::Nodes::Cat:0x00007ff1933087e8
@left=
#<Journey::Nodes::Cat:0x00007ff193308bf8
@left=#<Journey::Nodes::Slash:0x00007ff193308d38 @left="/", @memo=nil>,
@memo=nil,
@right=#<Journey::Nodes::Literal:0x00007ff193308c48 @left="posts", @memo=nil>>,
@memo=nil,
@right=#<Journey::Nodes::Slash:0x00007ff193308a40 @left="/", @memo=nil>>,
@memo=nil,
@right=#<Journey::Nodes::Symbol:0x00007ff1933086d0 @left=":id", @memo=nil, @regexp=/[^\.\/\?]+/>>,
@memo=nil,
@right=
#<Journey::Nodes::Group:0x00007ff193309c10
@left=
#<Journey::Nodes::Cat:0x00007ff193308220
@left=#<Journey::Nodes::Dot:0x00007ff1933084f0 @left=".", @memo=nil>,
@memo=nil,
@right=#<Journey::Nodes::Symbol:0x00007ff193308338 @left=":format", @memo=nil, @regexp=/[^\.\/\?]+/>>,
@memo=nil>>
Journey::Nodes::Cat
对象中的左/右属性是什么?是什么决定哪个令牌将“离开”哪个令牌将“正确”/
),“最里面的”(或叶子节点)?它不应该是“最外层”(或根节点)吗?Journey基于匹配路线的有限状态机,内置可视化器(需要graphviz):
File.open('routes.html', 'wt'){|f| f.write Rails.application.routes.router.visualizer }
Journey::Nodes::Cat
只是你可以遇到的节点类型之一,它是与路径expressions
中的grammar, see parser.y规则匹配的二进制节点,左边是第一个expression
,right
是所有其他,这产生了消耗所有表达式的循环。
关于外部路由分析的其他想法:路由不能转储到通用情况下的静态文件中,因为它们可以包含:
get :r, constraints: ->{rand(2)>0}
,想法是结果可以取决于请求,时间或状态之外的东西等)当这些存在时 - 偶数路由器本身可以在第二次运行时产生不同的结果同样的要求。但是对于简单的情况,你可以使用rails用于ActionDispatch::Routing::RoutesInspector
的rake routes
并获得更好的结构化路由信息,只需解析后一个输出。
在宝石routes_coverage
我这样做:
class Inspector < ActionDispatch::Routing::RoutesInspector
def collect_all_routes
res = collect_routes(@routes)
@engines.each do |engine_name, engine_routes|
res += engine_routes.map{|er|
er.merge({ engine_name: engine_name })
}
end
res
end
def collect_routes(routes)
routes.collect do |route|
ActionDispatch::Routing::RouteWrapper.new(route)
end.reject do |route|
route.internal?
end.collect do |route|
collect_engine_routes(route)
{ name: route.name,
verb: route.verb,
path: route.path,
reqs: route.reqs,
original: route,
}
end
end
res = Inspector.new(Rails.application.routes.routes.routes).collect_all_routes