It can be overkill to start every new Rails project as a bunch of microservices. Instead I tend to use this design pattern to separate concerns within my app into specific namespaces and scopes.
A simple example is to split functionality for regular users and admins:
# /config/routes.rb
# routes regular users
scope :app do
...
end
# routes for admin users
namespace :admin do
...
end
root "app/pages#index"
Notice that the admin user has its own controller namespace (e.g. Admin::UsersController
) and route subpath (e.g. /admin/users
), while the regular users are scoped to their own namespaced controllers (e.g. App::UsersController
) but their path remains the same (e.g. /users
).
The next step is to assign a specific (base) controller and layout that all resources in a scope/namespace will use.
# /app/controllers/admin/base_controller.rb
class Admin::BaseController < ApplicationController
include AdminHelper
layout "admin"
end
<!-- /app/views/layouts/admin.html.erb -->
<!DOCTYPE html>
<html lang='en'>
<head>
...
<%= stylesheet_link_tag "admin", media: "all" %>
<%= javascript_include_tag "admin" %>
<%= csrf_meta_tags %>
...
</head>
<body>
<%= yield %>
</body>
</html>
A few things to note here:
Admin::BaseController
.The reason why each layout includes its own asset tags is so that we can be more explicit about what styles and JS to include in each namespace. By default the Rails manifest looks something like this:
/* /app/assets/stylesheets/application.css */
/*
*= require_self
*= require_tree .
*/
Here the require_tree
statement loads in all CSS in the root folder. This isn't very useful for separating concern and instead I prefer my manifests to only load explicitly defined folders:
- app/
- assets/
- stylesheets/
admin.css
app.css
- app/
- admin/
- shared/
- javascripts/
admin.js
app.js
- app/
- admin/
- shared/
With this setup we can change the manifests to:
/* /app/assets/stylesheets/admin.css */
/*
*= require_self
*= require_tree ./shared
*= require_tree ./admin
*/
// /app/assets/javascripts/admin.js */
//= require_self
//= require_tree ./shared
//= require_tree ./admin
In the past this would work great in development but give you headaches in production. Luckily since about Rails 4 you will be warned to add these new manifest files to your list of precompiled assets:
# /config/application.rb
config.assets.precompile += ['app.css', 'app.js', 'admin.css', 'admin.js']
Lastly, by default Rails auto includes all helpers in every controller. This behaviour is nice in a simple app but does not help to separate functionality in this new setup. The following line disables this behaviour:
# /config/application.rb
config.action_controller.include_all_helpers = false
Now you can simply include all helpers for each namespace in their respective base controller.