Namespaced Controller - get all sub-controller
Posted by michael.schaerfer on 19-Nov-08 at 16:52
Let's say we have a pretty big rails application with lots of namespaced controller like:
1 class AdminController < ApplicationController ; end 2 class Admin::UsersController < AdminController ; end 3 class Admin::PostsController < AdminController ; end
...and so on.
Now we want to build a method to get all subcontroller of a given controller, so that we can say:
1 AdminController.subcontroller 2 # => [Admin::UsersController, Admin::PostsController]
For every namespaced controller, rails builts a module with the controller-name (without "Controller") for the sub-controller.
So, we add a class method to AdminController:
1 class << self ; 2 def subcontroller 3 self.to_s =~/^(.+)Controller$/ 4 return [] unless $1 5 mod = $1 6 smod = $1.demodulize 7 if( pmod = self.parent ).const_defined?(:"#{smod}") 8 return ( mod = mod.constantize ).constants.reject { |r| 9 !( mod.const_get( r ) < ActionController::Base ) 10 } 11 end 12 return [] 13 end 14 end
Looks complicated, so lets get through this:
First we get the module name of the current controller( "AdminController" => "Admin" ).
Then we check if the constant ("Admin") is defined and, if it is, we return all constants/classes of this module that inherit from ActionController::Base, thus being a controller class.
If the module is not defined, we return an empty array, no subcontroller.
(All this "const_get(..)" stuff is used for more nested controllers, like "Admin::System::Config::PostsController".)
Now all the ruby-geeks out there are screaming "way too complicated! why not use this:"
1 Object.subclasses_of( AdminController ) 2 # => [Admin::UsersController, Admin::PostsController]
..and this works too. But keep in mind that the Object#subclasses_of method cycles through the whole ObjectSpace, trying to find an inherited class.
A little benchmark test on script/console:
1 >> n = 5000 2 => 5000 3 >> Benchmark.bm do |x| 4 ?> x.report { n.times do ; AdminController.subcontroller ; end } 5 >> x.report { n.times do ; Object.subclasses_of( AdminController ) ; end } 6 >> end 7 user system total real 8 0.310000 0.000000 0.310000 ( 0.312271) 9 61.190000 0.280000 61.470000 ( 62.760276) 10 => true
.. so the subcontroller method, using Module#constants, is a little bit faster..
