SitePoint Sponsor

User Tag List

Results 1 to 11 of 11

Hybrid View

  1. #1
    SitePoint Zealot
    Join Date
    Sep 2000
    Location
    England
    Posts
    120
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Listing all controllers and actions in Rails

    I'm still getting the hang of both Ruby and Rails coming from PHP, but I'm writing a CMS at the moment and the permission system is based on the controllers, actions and IDs.

    In the admin area of the app, the administrator needs to be able to add new permissions and rather than have the admin type them in (requiring knowledge of the application code), I'd like to auto-list the controllers and actions within those controllers with select boxes.

    The time-consuming way is to keep a list of all the controllers and actions in the app, but it must be possible to list the controllers (by reading the files in the /controllers folder and getting the names from there) and to then retrieve the available methods in each controller class, but I don't know how I'd go about it in Ruby.

    Can anyone explain how I could get the list of controllers and then actions within those controllers or point at a resource somewhere else that explains it please?

    Thanks,

    Adam

  2. #2
    SitePoint Guru
    Join Date
    Aug 2005
    Posts
    986
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Code:
    controllers = []
    ObjectSpace.each_object(Class) do |c| 
      controllers << c if c.superclass == ActionController::Base
    end

  3. #3
    SitePoint Guru silver trophy Luke Redpath's Avatar
    Join Date
    Mar 2003
    Location
    London
    Posts
    794
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Nice snippet, but if you extend from your own controllers, wouldn't this always return false?:

    Code:
    if c.superclass == ActionController::Base
    Perhaps it should be:

    Code:
    if c.instance_of? ActionController::Base
    With actions (untested!):

    Code:
    controllers = []
    ObjectSpace.each_object(Class) do |c|
      if c.instance_of? ActionController::Base  
        controller = {:name => c, :actions => []}
        controller.public_methods(false).each { |m| controller[:actions] << m }
        controllers << controller
      end
    end

  4. #4
    SitePoint Guru
    Join Date
    Aug 2005
    Posts
    986
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I don't know if that will work:

    Code:
    irb(main):001:0> class A
    irb(main):002:1> end
    => nil
    irb(main):003:0> class B < A
    irb(main):004:1> end
    => nil
    irb(main):005:0> B.instance_of? A
    => false
    But you can do:

    Code:
    irb(main):007:0> B.ancestors
    => [B, A, Object, Kernel]
    So instead of superclass you do ancestors.include?

  5. #5
    SitePoint Guru silver trophy Luke Redpath's Avatar
    Join Date
    Mar 2003
    Location
    London
    Posts
    794
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Actually, looking at the API docs, there's a better way than that. Instead of instance_of?, you should use is_a? (or its alias kind_of? if you prefer).

    Code:
    class A
    end
    
    class B < A
    end
    
    b = B.new
    b.instance_of? B
    => true
    b.instance_of? A
    => false
    b.kind_of? A
    => true
    Thinking about it, you should probably convert the controller and method names to strings. And I noticed a mistake in my code - you need to call public_methods on c, not controller. Updated code:

    Code:
    controllers = []
    ObjectSpace.each_object(Class) do |c|
      if c.is_a? ActionController::Base  
        controller = {:name => c.to_s, :actions => []}
        c.public_methods(false).each { |m| controller[:actions] << m.to_s }
        controllers << controller
      end
    end

  6. #6
    SitePoint Zealot
    Join Date
    Sep 2000
    Location
    England
    Posts
    120
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks a lot for the code samples, I'll get them tested shortly and let you know how I get on.

  7. #7
    SitePoint Guru
    Join Date
    Aug 2005
    Posts
    986
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Luke, I don't think your code will work, because ObjectSpace.each_object(Class) will iterate over all *instances* of Class, that is, all classes. Then you ask: if c.is_a? ActionController::Base, which will never be true, because c is a Class, and not an ActionController::Base.

    Maybe this will work:

    Code:
    controllers = []
    ObjectSpace.each_object(Class) do |klass|
      if klass.ancestors.include? ActionController::Base
        controllers << {
                   :name => klass.controller_name, 
                   :methods => klass.send(:action_methods).map(&:to_s)}
      end
    end

  8. #8
    SitePoint Zealot
    Join Date
    Sep 2000
    Location
    England
    Posts
    120
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Just got round to trying this and it occurred that this doesn't quite do what I'm after as I need a list of all controllers in the application, not just those used in the current page (which ObjectSpace gives you).

    I presume to do this I'll need to read the list of files in /app/controllers?

    Aside from that though, there was only one error in the code - it doesn't like the 'ancestors.include? ActionController::Base' or '.is_a? ActionController::Base' calls, throwing this exception:

    uninitialized constant Base

    If I'm going to just read the classes in the /controllers dir though, I guess it gets around this problem anyway.

    Thanks again for all your help and any further advice you can give on traversing the /controllers directory

    Will I just need to include the files then do something along the lines of
    Code:
    ControllerName::public_methods(false).each ...
    ?

  9. #9
    SitePoint Guru
    Join Date
    Aug 2005
    Posts
    986
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I think that will work, but I don't know how fast that will be...

  10. #10
    SitePoint Zealot
    Join Date
    Sep 2000
    Location
    England
    Posts
    120
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I've got it reading the list of files and working out the class names with some regex:
    Code:
    @controllers = []
    		cs = Dir.entries(RAILS_ROOT + '/app/controllers')
    		for ctrl in cs
    			ctrl = ctrl.sub(/([a-z])/) {|match| match.upcase}
    			ctrl = ctrl.sub(/_([a-z])/) {|match| match.upcase.sub(/_/, '')}
    			ctrl = ctrl.sub(/.rb/, '')
    			@controllers << ctrl
    		end
    After a quick test, using ControllerName:ublic_methods it gives a huge number of extra inherited methods that aren't actions, so the class has to be instantiated before it gives the 'correct' method list.

    My problem now is how to create an instance with a variable class name. In PHP I'd just do 'new $var();' but I'm not sure how I'd do it in Ruby.

    On the speed issue, it's not too big a problem - this is only to be run once on one page of the admin section when adding a new permission to a user or group so it's not going to affect 99% of the site.

  11. #11
    SitePoint Zealot
    Join Date
    Sep 2000
    Location
    England
    Posts
    120
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I think I've found it:
    Code:
    obj = Object::const_get(class_name).new()
    Is this the normal way to do it? Just found the code with a Google search so want to make sure it's not poor code.

    EDIT: Scrap that, class_name isn't a constant

    EDIT 2: That did work, never mind and thanks for all the help


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •