Попытка понять Journey :: Path :: Pattern # spec (внутренняя маршрутизация Rails) - PullRequest
0 голосов
/ 03 апреля 2019

Контекст: Я решаю проблему, когда мне нужна программа внешнего аудита, чтобы понимать и «применять» маршруты 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 объекте?Что решает, какой токен будет «левым», а какой - «правым»
  • Это подозрительно похоже на двоичное дерево, но почему самый первый токен (то есть первый /), «самый внутренний»"(или листовой узел)?Разве это не должен быть «самый внешний» (или корневой узел)?
  • Какой эффективный способ пройти по этой структуре данных при выполнении сопоставления маршрутов?

1 Ответ

0 голосов
/ 12 апреля 2019

Путешествие на основе конечного автомата, который соответствует маршруту, есть встроенный визуализатор (требуется графвиз):

File.open('routes.html', 'wt'){|f| f.write Rails.application.routes.router.visualizer }

Journey::Nodes::Cat - это только один из типов узлов, с которым вы можете столкнуться, это двоичный узел, который соответствует правилу expressions в пути грамматика, см. Parser.y , слева сначала expression, right - это все остальные, это создает цикл, который потребляет все выражения.

Другие мысли об анализе внешних маршрутов: маршруты не могут быть выгружены в статический файл в общем случае, потому что они могут содержать:

  • динамические ограничения с не-чистыми функциями (например - get :r, constraints: ->{rand(2)>0}, идея заключается в том, что результат может зависеть от чего-то вне запроса, времени или состояния и т. Д.) Если они присутствуют - даже сам маршрутизатор rails может выдает другой результат при втором запуске по тому же запросу.

  • приложения для встраиваемых стоек - могут иметь жестко закодированные или не рельсовые маршрутизаторы

  • rails engine - имеют rails router, что проще, чем обычные приложения в стойке, но обман с точками монтирования и слиянием с областью основного приложения

Но для простого случая вы можете подключиться к рельсам 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
...