In our app directory, we want some of the sub-directories to contain namespaced classes, and some that contain top-level classes. For example:
app/models/user.rbdefines::Userapp/operations/foo.rbdefines::Operations::Fooapp/operations/user/foo.rbdefines::Operations::User::Foo
Our application.rb contains the following configuration:
config.paths = Rails::Paths::Root.new(Rails.root)
config.paths.add 'app/models', eager_load: true
config.paths.add 'app', eager_load: true
This works fine in most cases, but sometimes in development mode and with Rails' autoreloading turned on, this leads to the wrong classes being loaded. For instance ::User is mistaken for Operations::User and vice-versa.
Is there a way to configure this behavior so that it works without any errors?
If not, the only workaround I can think of is to create a second directory for "namespaced" classes, along the lines of app and app_namespaced. Or else app/namespaced, since app-level code should reside within app. But these seem like ugly workarounds to me.
Edit: A little example as asked for by @dgilperez:
# app/models/user.rb
class User
end
# app/models/group.rb
class Group
def some_method
# Since we're in a top-level namespace, User should always
# resolve to ::User. But, depending on some seemingly random
# factors, it sometimes resolves to Operations::User.
User.new
end
end
# app/operations.rb
module Operations
end
# app/operations/user/create.rb
module Operations::User
class Create
def some_method
# Here, as expected, I need to prefix with "::" as
# 'User' would refer to the module we're currently in.
# That's fine and works.
::User.new
end
end
end
Yes, this is a downside of rails' autoloading. By default, it loads everything from
/app, but first level of directory structure is not part of the names. It's so thatapp/models/user.rbcan defineUser, not require it to beModels::User.You don't need to mess with the load paths. Several approaches/workarounds available here.
In my current project we just double the namespacing directory. Meaning that if we want to define
ServiceObjects::User::Import, we put it intoapp/service_objects/service_objects/user/import.rbI personally prefer a variation of that approach, which is to put all "non-standard" stuff into
app/lib(can beapp/customor anything you want). This way, there's no weird duplication of directory names and all custom code is nicely contained.