Is there a way to tell Rails that all files in a certain folder are contained in a certain namespace?
Ie.
I have a file bar.rb
in app/foo
. Rails will assume this file defines Bar
, but instead I want this file to define Foo::Bar
.
I know I can achieve this by adding my root to Rails' autoload paths, but that isn't a real solution. Is there any other way I can tell Rails that all files within app/foo
reside in the Foo
namespace?
EDIT: File tree for clarification
app
assets
controllers
models
foo
bar.rb
quux.rb
I would like to be able to define Foo::Bar
and Foo::Quux
in respectively bar.rb
and quux.rb
, while also using Rails autoloading. Without having to resort to the tree structure as below:
app
assets
controllers
models
foo
foo
bar.rb
quux.rb
Rails automatically reloads classes and modules if application files in the autoload paths change. More precisely, if the web server is running and application files have been modified, Rails unloads all autoloaded constants managed by the main autoloader just before the next request is processed.
The Module#autoload method registers a file path to be loaded the first time that a specified module or class is accessed in the namespace of the calling module or class.
Namespace in Ruby allows multiple structures to be written using hierarchical manner. Thus, one can reuse the names within the single main namespace. The namespace in Ruby is defined by prefixing the keyword module in front of the namespace name. The name of namespaces and classes always start from a capital letter.
In general, Rails doesn’t encourage developers to factor code into namespaces. Most apps end up with hundreds of files and very few directories in app/models and similarly flat hierarchies in other app directories.
More precisely, if the web server is running and application files have been modified, Rails unloads all autoloaded constants just before the next request is processed. That way, application classes or modules used during that request are going to be autoloaded, thus picking up their current implementation in the file system.
It can’t simply use Ruby’s built-in autoload, because that needs to know both the name and file location of each constant up front, and Rails knows neither of these things at boot.
In a Rails application file names have to match the constants they define, with directories acting as namespaces. For example, the file app/helpers/users_helper.rb should define UsersHelper and the file app/controllers/admin/payments_controller.rb should define Admin::PaymentsController.
You can autoload files with namespaces corresponding to directories inside /app
by adding /app
to your autoload paths in your application config.
# config/application.rb
config.autoload_paths << "#{config.root}/app"
I'm not sure whether or not this is what the author of the question meant by
I know I can achieve this by adding my root to Rails' autoload paths, but that isn't a real solution.
But this is definitely a real solution and the correct one. Doing this is fine and not at all unusual in my experience.
There are only minor side effects which are the cost of adopting the inconsistent naming conventions you want. If you refer to an undefined constant that matches the name of another directory in the app you'll get a slightly different error message.
# Models::Foo => LoadError (Unable to autoload constant Models::Foo, expected /app/models/foo.rb to define it)
But if you have existing namespaces that match directories in app
loading will still work fine. Here's what Rails is doing:
It takes the paths added to config.autoload_paths
and adds them to the defaults (the directories under app
: app/models
, app/controllers
, app/foo
etc). Then when a constant is referenced that is not already loaded Rails proceeds through those paths, looking for paths that match the constants. So when you reference Foo::Bar
it looks for app/models/foo/bar.rb
, app/controllers/foo/bar.rb
etc. until it finds a file that defined Foo::Bar
. All we're doing is adding app/foo/bar.rb
to that lookup.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With