Jonathan
Jonathan

Reputation: 11494

Rails get list of routes as paths with parameters

I want to have a list of URLs my Rails app supports, but I do not want to provide any arguments ahead of time.

For example, I want to list the path for user_registration_path as /%{locale}/users/sign_up.

I can get a list of named routes like so (testing in Rails console):

Rails.application.routes.named_routes.helper_names

Example output:

["rails_info_properties_path", "rails_info_routes_path", "rails_info_path", "rails_mailers_path", "rails_service_blob_path", "rails_blob_path", "rails_blob_representation_path", "rails_representation_path", "rails_disk_service_path"...]

Is there any way to achieve this in Rails?

Upvotes: 0

Views: 3289

Answers (2)

max
max

Reputation: 102174

You can do this quite simply by looking at how rails dumps the routes with the rails routes command.

# frozen_string_literal: true

require "rails/command"

module Rails
  module Command
    class RoutesCommand < Base # :nodoc:
      class_option :controller, aliases: "-c", desc: "Filter by a specific controller, e.g. PostsController or Admin::PostsController."
      class_option :grep, aliases: "-g", desc: "Grep routes by a specific pattern."
      class_option :expanded, type: :boolean, aliases: "-E", desc: "Print routes expanded vertically with parts explained."

      def perform(*)
        require_application_and_environment!
        require "action_dispatch/routing/inspector"

        say inspector.format(formatter, routes_filter)
      end

      private
        def inspector
          ActionDispatch::Routing::RoutesInspector.new(Rails.application.routes.routes)
        end

        def formatter
          if options.key?("expanded")
            ActionDispatch::Routing::ConsoleFormatter::Expanded.new
          else
            ActionDispatch::Routing::ConsoleFormatter::Sheet.new
          end
        end

        def routes_filter
          options.symbolize_keys.slice(:controller, :grep)
        end
    end
  end
end

The key here is:

inspector.format(formatter, routes_filter)

Where formatter is really just a class that responds to section, header and result:

# formats routes as a simple array of hashes
class HashFormatter

  def initialize
    @buffer = []
  end

  # called for the main routes and also for each 
  # mounted engine
  def section(routes)
    routes.each do |r|
      @buffer << r.slice(:name, :verb, :path)
    end
  end

  # this method does not need to do anything since the "headers" are
  # part of the hashes
  def header(routes)
  end

  def result
    @buffer
  end
end

We can then invoke our formatter with:

inspector = ActionDispatch::Routing::RoutesInspector.new(Rails.application.routes)
inspector.format(HashFormatter.new)

And get an array of hashes:

[{:name=>"", :verb=>"GET", :path=>"/pizzas/:foo(.:format)"}, {:name=>"", :verb=>"GET", :path=>"/pizzas/:foo/:bar(.:format)"}, {:name=>"", :verb=>"GET", :path=>"/pizzas/:foo/:bar/:baz(.:format)"}, {:name=>"foo", :verb=>"DELETE", :path=>"/foo(.:format)"}, {:name=>"root", :verb=>"GET", :path=>"/"}, #...]

The advantage here is that you're piggybacking on existing code that gathers the routes for any mounted engines and rejects internal routes.

Upvotes: 3

nuaky
nuaky

Reputation: 2086

you can try to work on this command:

Rails.application.routes.routes
  .map { |r| { path: r.path.spec.to_s } }

it will give you this output:

{:path=>"/admin/users/:id/edit(.:format)"},
{:path=>"/admin/users/:id(.:format)"},
{:path=>"/admin/users/:id(.:format)"},
{:path=>"/admin/users/:id(.:format)"},

Or as an array (you can improve it, it's just a quick example):

Rails.application.routes.routes
  .flat_map { |r| r.path.spec.to_s }
  .uniq
  .map { |path| path.gsub('(.:format)', '') }
[
 "/admin/users",
 "/admin/users/new",
 "/admin/users/:id/edit",
 "/admin/users/:id"
]

Upvotes: 7

Related Questions