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. Rails Engines are a great way to pull your code into isolated, reusable modules, but there’s a significant amount of overhead associated with an Engine that might be too expensive for a small feature, or you might just have a few conceptually similar models that you want to “live near each other”.
Rails has support for namespacing in its MVC architecture, but there are a few things that might cause confusion if you haven’t used it before.
Pretend your app has a user-configurable dashboard. Users can add widgets — like charts and tables — to the dashboard, and they can lay the widgets out on the dashboard in rows and columns. You might model it like this:
Dashboard::Dashboard, which contains
Dashboard::Row, which contains
Dashboard::Widget, which has a position within its
I’ll use these models in the examples below.
Most of the gnarly bits of namespacing happen in the models, so I’ll start there.
First, you’ll need to create a module for your namespace and define the class method
table_name_prefix (see Migrations):
Once you’re “inside” the namespace, you generally won’t refer to it when declaring associations. For example,
Widget will belong to
Row will belong to
class Widget < ApplicationRecord
class Row < ApplicationRecord
class Dashboard < ApplicationRecord
Outside the namespace, though, you’ll have to tell Rails the class name of any association that uses your namespaced model. Otherwise, it will guess incorrectly:
class User < ApplicationRecord
# WRONG => NameError: uninitialized constant User::DashboardDashboard
has_many :dashboard_dashboards, class_name: 'Dashboard::Dashboard'
# ALSO RIGHT
has_many :dashboards, class_name: 'Dashboard::Dashboard'
Since most databases don’t support namespaces for tables, Rails expects you to prefix your table names with the “underscored” name of your namespace. You also won’t be able to pass
foreign_key: true to the
references helper method, so you’ll have to create constraints yourself.
To create the migration for
$ rails generate migration create_dashboard_widgets
Edit the generated file:
class CreateDashboardWidgets < ActiveRecord::Migration
create_table :dashboard_widgets do |t|
# adding "foreign_key: true" here will cause an error like this:
# PG::UndefinedTable: ERROR: relation "rows" does not exist
t.references :row, index: true
add_foreign_key :dashboard_widgets, :dashboard_rows, column: :row_id
Simply wrap your routes in a call to
namespace :dashboard do
Your URLs will look like
/dashboard/dashboards/1, and path helpers will look like
Controllers are also straightforward:
class DashboardsController < ApplicationController
@dashboards = current_user.dashboards
You may get a confusing error if you try to render a partial of a model from outside the namespace.
Say one of your widgets renders a list of recently active users:
<%# This can result in an error:
Missing partial dashboard/users/_user %>
<%= render @widget.active_users %>
Rails is attempting to render a different partial than you might expect since you’re inside a namespace. The idea is that you might want a different
_user partial in, say, an admin area than you do in the public area.
Since I’m generally using namespaces to isolate features rather than create distinct layouts, I turn this behavior off:
config.action_view.prefix_partial_path_with_controller_namespace = false
Unfortunately, it’s a global setting, so you can’t change it for each call to
render. You can always explicitly render a template if you need the default behavior:
<%= render partial: 'admin/users/user', collection: @users %>
If you’re using FactoryGirl for testing, you’ll need to tell her the name of the factory to use for your namespaced associations:
factory :dashboard_widget, class: Dashboard::Widget do
association :row, factory: :dashboard_row
After many years of cramming everything directly into
app/models, I’ve had great luck with namespacing. It keeps related concepts together and provides a nice level of isolation without the overhead of full-fledged Engines or microservices.
I hope your experience is as pleasant as mine!