# frozen_string_literal: true module Chusaku # Handles extracting information about the Rails project's routes. class Routes class << self # Primary method to call. # # Example output: # # { # 'users' => { # 'edit' => [ # { verb: 'GET', path: '/users/:id', name: 'edit_user' } # ], # 'update' => [ # { verb: 'PATCH', path: '/users', name: 'edit_user' }, # { verb: 'PUT', path: '/users', name: 'edit_user' } # ] # }, # 'empanadas' => { # 'create' => [ # { verb: 'POST', path: '/empanadas', name: nil } # ] # } # } # # @return [Hash] Routes hash def call routes = {} Rails.application.routes.routes.each do |route| controller, action, defaults = extract_data_from(route) routes[controller] ||= {} routes[controller][action] ||= [] add_info_for \ route: route, routes: routes, controller: controller, action: action, defaults: defaults end backfill_routes(routes) end private # Adds formatted route info for the given param combination. # # @param route [Hash] Route info # @param routes [Hash] Collection of all route info # @param controller [String] Controller key # @param action [String] Action key # @param defaults [Hash] Default parameters for route # @return [void] def add_info_for(route:, routes:, controller:, action:, defaults:) verbs_for(route).each do |verb| routes[controller][action] .push(format(route: route, verb: verb, defaults: defaults)) routes[controller][action].uniq! end end # Extract the HTTP verbs for a Rails route. Required for older versions of # Rails that return regular expressions for a route verb which sometimes # contains multiple verbs. # # @param route [ActionDispatch::Journey::Route] Route given by Rails # @return [Array] List of HTTP verbs for the given route def verbs_for(route) route_verb = route.verb.to_s %w[GET POST PUT PATCH DELETE].select do |verb| route_verb.include?(verb) end end # Formats information for a given route. # # @param route [ActionDispatch::Journey::Route] Route given by Rails # @param verb [String] HTTP verb # @param defaults [Hash] Default parameters for route # @return [Hash] { verb => String, path => String, name => String } def format(route:, verb:, defaults:) { verb: verb, path: route.path.spec.to_s.gsub('(.:format)', ''), name: route.name, defaults: defaults } end # Given a routes hash, backfill entries that aren't already filled by # `Rails.application.routes`. # # @param routes [Hash] Routes hash generated by this class # @return [Hash] Backfilled routes hash def backfill_routes(routes) paths = {} routes.each do |_controller, actions| actions.each do |_action, data| data.each do |datum| paths[datum[:path]] ||= datum[:name] datum[:name] ||= paths[datum[:path]] end end end routes end # Given a route, extract the controller and action strings. # # @param route [ActionDispatch::Journey::Route] Route instance # @return [Array] (String, String, Hash) def extract_data_from(route) defaults = route.defaults.dup controller = defaults.delete(:controller) action = defaults.delete(:action) [controller, action, defaults] end end end end