Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails class loading skips namespaced class when another class of same name in root namespace is loaded

I have two namespaces, each with its own controller and presenter classes: Member::DocumentsController Member::DocumentPresenter Guest::DocumentsController Guest::DocumentPresenter

Both presenters inherit from ::DocumentPresenter.

Controllers access their respective presenters without namespace specified, e.g.:

class Guest::DocumentsController < ActionController::Base
    def show
        DocumentPresenter.new(find_document)
    end
end

This usually calls presenter within same namespace. However sometimes in development environment I see base ::DocumentPresenter is being used.

I suspect the cause is that base ::DocumentPresenter is already loaded, so Rails class auto-loading doesn't bother to look further. Is this likely the case? Can it happen in production environment too?

I can think of two solutions:

  • rename base class to DocumentPresenterBase
  • explicitly require appropriate presenter files in controller files

Is there a better solution?

like image 307
Wojtek Kruszewski Avatar asked Nov 05 '12 16:11

Wojtek Kruszewski


2 Answers

You are correct in your assumptions - If you do not specify namespace, Ruby starts from current namespace and works its way up to find the class, and because the namespaced class is not autoloaded yet, the ::DocumentPresenter is found and autoloader does not trigger.

As a solution I would recommend renaming ::DocumentPresenter to DocumentPresenterBase, because this protects you from bugs when you forget namespacing or explicit requiring somewhere.

The second option to consider would actually be using specific namespaced classnames all over the place, but this suffers from bugs when you accidentally forget to namespace some call.

class Guest::DocumentsController < ActionController::Base
  def show
    Guest::DocumentPresenter.new(find_document)
  end
end 

Third option would be your second - explicitly require all the classes in initializer beforehand. I have done this with Rails API which receives embedded models in JSON and Rails tends to namespace them when the actual models are not loaded yet.

Option 3.5 You could probably trick autoloader to do the heavy lifting (though, this might seem more like a hack):

class Guest::DocumentsController < ActionController::Base

  # trigger autoload
  Guest::DocumentPresenter

  def show
    # This should refer Guest::DocumentPresenter
    DocumentPresenter.new(find_document)
  end

  def show
    # As will this
    DocumentPresenter.new(find_document)
  end
end 

Still the cleanest would be to rename the base class.

like image 116
Laas Avatar answered Nov 17 '22 01:11

Laas


I think in 3 solutions if you want to mantein the name, one is your second solution.

1) explicitly require appropriate presenter files in controller files

2) Execute the full environment class path, like:

class Guest::DocumentsController < ActionController::Base
    def show
        Guest::DocumentPresenter.new(find_document)
    end
end

3) Create a file on initialize directory and execute require manually (the worst options :S)

like image 40
Andrés Avatar answered Nov 17 '22 00:11

Andrés