38

In rails, I can get the name of the current controller via controller_name and the current action by calling action_name. I am looking for similar runtime reflection for fetching the following:

  1. List of all controllers in the application.
  2. List of all actions in a given controller.

For example, I a Product controller which has "add" and "edit" actions. Can I pull the names of these actions programmatically to show the user what operations are supported?

I have looked at a method that calls for use of ActionController::Routing::Routes.named_routes.routes.each but I could not get that to work. I got uninitialized constant ActionDispatch::Routing::Routes error when I used it.

If there is any good tutorial or document that can help me understand how rails reflection capabilities. I searched for it but I mostly got active record reflection related blogs. I am looking for something that lets me get information on controllers and actions/methods at run time.
Thanks,

Tabrez

mu is too short
  • 426,620
  • 70
  • 833
  • 800
Tabrez
  • 3,424
  • 3
  • 27
  • 33
  • Are you doing this at run-time or in a rake job. run-time is a bad idea. Rake job is easy to do this. – drhenner Dec 31 '11 at 05:37
  • Did you mean "rake routes"? I was looking for something at run time. I don't need the extensive information that rake routes outputs. I just need the controller names, which I assume must be available as a collection. Being a noob to rails, I just don't know what context objects are accessible in rails at runtime. – Tabrez Dec 31 '11 at 06:52
  • Found similar question here, http://stackoverflow.com/questions/1564278/how-to-programmatically-list-all-controllers-in-rails – nkm Dec 31 '11 at 07:17
  • @nkm: I had checked that question. It is similar to an extent but the accepted answer was not in line with the information I was looking for. – Tabrez Dec 31 '11 at 11:28

5 Answers5

118

The easiest way to get a list of controller classes is:

ApplicationController.descendants

However, as classes are loaded lazily, you will need to eager load all your classes before you do this. Keep in mind that this method will take time, and it will slow down your boot:

Rails.application.eager_load!

To get all the actions in a controller, use action_methods

PostsController.action_methods

This will return a Set containing a list of all of the methods in your controller that are "actions" (using the same logic Rails uses to decide whether a method is a valid action to route to).

Yehuda Katz
  • 28,535
  • 12
  • 89
  • 91
  • That's exactly was what I was looking for. Thanks a lot! Is there any particular rails resource/guide/book that might help in understanding these type of matters; more specifically page lifecycle and runtime objects that rails goes through? Any pointers will be a huge help as I am new to rails. – Tabrez Dec 31 '11 at 11:38
  • 3
    It's worth mentioning that by calling `.action_methods` you only get the list of public methods (=actions) defined in a class. Since Rails can render template without actual method in controller, it can possibly lead to security hole in the application. – Artur INTECH Mar 03 '15 at 07:27
11

PostsController.action_methods will return all actions of PostsController including inherited, it's not what I want, I found PostsController.instance_methods(false), which will return all instance methods of PostsController and not include inherited, exactly what I want.

Fangxing
  • 5,716
  • 2
  • 49
  • 53
5

I had a similar problem; in my case it was for writing controller tests. There are certain basic tests that I want to run on every action for certain controllers (e.g., return 401 if the user is not logged in). The problem with the action_methods solution is that it picks up all methods on the controller class, even if the method does not have a corresponding route in config/routes.rb.

Here's what I ended up doing:

def actions_for_controller(controller_path)
  route_defaults = Rails.application.routes.routes.map(&:defaults)
  route_defaults = route_defaults.select { |x| x[:controller] == controller_path }
  route_defaults.map { |x| x[:action] }.uniq
end

So if you wanted a list of actions for PostsController, you'd say:

actions_for_controller('posts')

And you would get:

['show', 'create', 'edit', 'destroy'] (or whatever)

If you wanted a list of all controllers, something like this should do it:

def controllers_list
  route_defaults = Rails.application.routes.routes.map(&:defaults)
  files = route_defaults.map { |x| "#{x[:controller]}_controller" }.uniq
  files -= ['_controller']
  files.map { |x| x.split('/').map(&:camelize).join('::').constantize }
end
  • For some reason i needed this as well since the .intance_methods(false) is only working inside the application, but when i tried to run rake swagger:docs in my case, i was getting empty array, and your mentioned method has worked perfectly – Amir El-Bashary Jan 08 '20 at 09:06
5

We are cleaning out unused controllers and actions. So I wanted to create an exhaustive list to mark for deletion. Here's what I ended up doing

ApplicationController.descendants.each do |controller|
  puts controller.name
  controller.action_methods.each do |action|
    puts '  ' + action
  end
end

That provides a nice list with actions indented under controller names.

Abram
  • 39,950
  • 26
  • 134
  • 184
0

TLDR

you check this on your rails console

all_controllers = Rails.application.routes.routes.map do |route|
  route.defaults[:controller]
end.uniq.compact

controllers = all_controllers.reject { |x| x =~ /active_storage|rails|action_mailbox|devise|service_worker/ }
controller_with_actions = controllers.map do |name|
  controller = if name.match?('/') 
      splits = name.split('/')
      prefix = splits[0..-2].map(&:capitalize)
      suffix = [splits[-1]].map(&:classify)
      (prefix + suffix).join('::').pluralize
    else 
      name.classify.pluralize
    end
  verified_controller = controller.concat('Controller').constantize rescue controller.singularize.concat('Controller').constantize rescue next
  [verified_controller.to_s, verified_controller&.public_instance_methods(false)&.to_a] rescue next
end.compact 

# [
#   ["DashboardController", ["index"]],
#   ["UsersController", ["index", "new", "create", "show", "update", "destroy"]]
#   ["Api::V1::StaticController", ["index", "new", "create", "show", "update", "destroy"]]
# ] 

this already tested also in Rails 7.0 and other lower versions. because ApplicationController.descendants didn't work at all at 7th version of rails.

or you can use use my class on my public github gist ControllerActionList link

mirzalazuardi
  • 45
  • 1
  • 8