Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to verify controller actions are defined for all routes in a rails application?

Is there a way to verify that all controller actions, as defined in config/routes.rb and exposed by rake routes, actually correspond to an existing controller action?

For example, suppose we have the following routes file:

Application.routes.draw do
  resources :foobar
end

And the following controller:

class FoobarsController < ApplicationController
  def index
    # ...
  end

  def show
    # ...
  end
end

I'd like to have some way of auto-detecting that the create, new, edit, update and destroy actions (as implicitly defined by the routes) are not mapped to a valid controller action - so that I can fix the routes.rb file:

Application.routes.draw do
  resources :foobar, only: [:index, :show]
end

An "integrity check" of the routes, if you will.

Such a check wouldn't necessarily need to be perfect; I could easily verify any false positives manually. (Although a "perfect" check would be ideal, as it could be included in the test suite!)

My motivation here is to prevent AbstractController::ActionNotFound exceptions from being raised by dodgy API requests, as additional routes were inadvertently defined (in a large application).

like image 905
Tom Lord Avatar asked Oct 19 '18 11:10

Tom Lord


People also ask

How does routing work in rails?

When your application receives a request, the routing will determine which controller and action to run, then Rails creates an instance of that controller and runs the method with the same name as the action. class ClientsController < ApplicationController def new end end

How does the rails router know which controller to use?

To make it simple, when you enter a url in your domain, the rails router will know which controller and action to handle your url In the diagram above, a request made by a user to the URL /pages/home goes through the browser to the Rails Router ( first blue box above ).

What is dispatcher in rails?

Dispatcher is our entry point back into Rails land. It knows that a request is served by a controller, and it knows that the way to talk to a Rails controller is to send it a #dispatch method and pass along the action, the request object, and a fresh new ActionDispatch::Response object to write the response into.

What is a routeset in rails?

It’s really a DSL to the public interface. The RouteSet is the actual class that acts as the entry point for route configuration in a Rails application. It’s most famous for the #draw method, which we’ve just used in routes.rb:


1 Answers

I got curious, and the following is my attempt. It's still not accurate because it does not yet match proper format. Also, some routes have constraints; my code doesn't yet consider.

rails console:

todo_skipped_routes = []
valid_routes = []
invalid_routes = []

Rails.application.routes.routes.each do |route|
  controller_route_name = route.defaults[:controller]
  action_route_name = route.defaults[:action]

  if controller_route_name.blank? || action_route_name.blank?
    todo_skipped_routes << route
    next
  end

  # TODO: maybe Rails already has a "proper" way / method to constantize this
  # copied over @max answer, because I forgot to consider namespacing
  controller_class = "#{controller_route_name.sub('\/', '::')}_controller".camelcase.safe_constantize

  is_route_valid = !controller_class.nil? && controller_class.instance_methods(false).include?(action_route_name.to_sym)

  # TODO: check also if "format" matches / gonna be "responded to" properly by the controller-action
  #   check also "lambda" constraints, and `request.SOMEMETHOD` constraints (i.e. `subdomain`, `remote_ip`,  `host`, ...)

  if is_route_valid
    valid_routes << route
  else
    invalid_routes << route
  end
end

puts valid_routes
puts invalid_routes

# puts "friendlier" version
pp invalid_routes.map(&:defaults)
# => [
#  {:controller=>"sessions", :action=>"somenonexistingaction"},
#  {:controller=>"posts", :action=>"criate"},
#  {:controller=>"yoosers", :action=>"create"},
# ]

I am also interested to know other answers, or if there is a proper way to do this. Also, if anyone knows an improvement over my code, please let me know. Thanks :)

like image 125
Jay-Ar Polidario Avatar answered Dec 02 '22 16:12

Jay-Ar Polidario