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).
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
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 ).
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.
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:
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 :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With