1
votes

I have the following code to pull out and instantiate Rails controllers:

def get_controller(route)
  name = route.requirements[:controller]
  return if name.nil?


  if name.match(/\//)
    controller_name = name.split('/').map(&:camelize).join('::')
  else
    controller_name = name.camelize
  end

  p controller_name: controller_name

  controller = Object.const_get("#{controller_name}Controller")

  p controller: controller
  controller.new
end

some routes are single names - "users", "friends", "neighbors", "politicians", etc...

other routes are nested, such as "admin/pets", "admin/shopping_lists", "admin/users", etc...

The above code works (in that it properly builds and instantiates the controller) in most of the cases mentioned, except for one - in this example, "admin/users"

from the puts statements, I'm getting the following:

{:controller_name=>"Admin::Users"}
{:controller => UsersController}

You'll notice that the namespace Admin is getting cut off. My guess is that since this is only the case for controllers which share a name in multiple namespaces (users and admin/users), it has something do to with Rails autoloading (?). Any idea about what is causing this?

As per the comment from lx00st, I should also point out that I've tried various forms of getting these constants, another attempt was as follows:

sections = name.split('/')
sections.map(&:camelize).inject(Object) do |obj, const|
  const += "Controller" if const == sections.last
  obj.const_get(const)
end

The same problem was encountered with this approach.

2
In this case const_get looks for a constant in Object module. You should end with something that call Admin.const_get("UsersController") - lx00st
I tried that as well - I'll add that to the OP - dax
And if this called directly from rails console? Admin.const_get("UsersController") - lx00st
and tell your ruby version - lx00st
No offence to the design approach you have selected... I just wonder how efficient it is (it might be expensive for go through these lookups for every request), as well if the approach itself could handle name-spacing. - Myst

2 Answers

3
votes

This was solved by an answer from user apneadiving which can be found here

Be aware that there are vicious cases in Rails development mode. In order to gain speed, the strict minimum is loaded. Then Rails looks for classes definitions when needed.

But this sometimes fails big time example, when you have say ::User already loaded, and then look for ::Admin::User. Rails would not look for it, it will think ::User does the trick.

This can be solved using require_dependency statements in your code.

Personally I think this is a bug, not a feature, but...so it goes. It solves the problem.

1
votes

First of all, this code is superfluous:

if name.match(/\//)
  controller_name = name.split('/').map(&:camelize).join('::')
else
  controller_name = name.camelize
end

The only string would perfectly handle both cases:

controller_name = name.split('/').map(&:camelize).join('::')

Then, you probably want to handle namespaces properly:

n_k = controller_name.split('::')
klazz = n_k.last

namespace_object = if n_k.length == 1
                     Object
                   else
                     Kernel.const_get(n_k[0..-2].join('::'))
                   end

controller = namespace_object.const_get("#{klazz}Controller")

Hope that helps.