When writing a rails app, we often have to deal with multiple namespaces. The most common case is having classic actions and some others in an admin namespace. Some actions may have same views between namespaces.
For exemple, you could have a ProjectsController
with an action index
:
class ProjectsController < ApplicationController
def index
# do something
end
end
Calling /projects
action renders app/views/projects/index.html.erb
template.
Let's say you also have a Admin::ProjectsController
with a index
action :
class Admin::ProjectsController < ApplicationController
def index
# do something
end
end
Calling admin/projects
action renders app/views/admin/projects/index.html.erb
template.
Those actions renders differents views, but what if we want to render the same template for both ?
- You can call manually
render 'projects/show'
in the corresponding controller's action in admin namespace
class Admin::ProjectsController < ApplicationController
def index
# do something
render 'projects/index'
end
end
- You can also call manually
<%= render template: 'projects/show' %>
inapp/views/admin/projects/show.html.erb
Don't you think it could be better if we could find a mechanism to render automatically app/views/admin/projects/index.html.erb
if exists, otherwise fallback to app/views/projects/index.html.erb
?
By default, paths prefixes where Rails will looks for templates (for a given controller) can be retreived with, in a rails console :
Admin::ProjectsController.new.send(:_prefixes)
In our case, it'll return ["admin/projects", "application"]
. Admin::ProjectsController
inherits from ApplicationController
, that's why there is "application"
in the returned array. Those element are sorted by priority. For the index
action, Rails will first look in app/views/projects/index.html.erb
, then app/views/application/index.html.erb
, then raise a MissingTemplate
exception.
We can add or remove elements to this array, overriding in a controller local_prefixes
class method. If you want for the whole namespace to fallback to another path if view doesn't exist, then create a Admin::BaseController
that Admin::ProjectsController
will inherits from, with :
class Admin::BaseController < ApplicationController
def self.local_prefixes
[controller_path, controller_path.sub(/^admin\//, '')]
end
end
and
class Admin::ProjectsController < Admin::BaseController
def index
# do something
end
end
The local_prefixes
method returns an array of prefixes. By default, this method is defined in action_view/view_path.rb
:
# Override this method in your controller if you want to change paths prefixes for finding views.
# Prefixes defined here will still be added to parents' <tt>._prefixes</tt>.
def local_prefixes
[controller_path]
end
Adding another element to this array will make rails search for partials along those paths. Don't forget about the priority. Here, Rails will first look for controller_path
, which is "admin/projects"
for Admin::ProjectsController
, then if app/views/admin/projects/index.html/erb
doesn't exist, will look for controller_path.sub(/^admin\//, '')
, which will now be "projects"
We can check that calling in a rails console
Admin::ProjectsController.new.send(:_prefixes) # `["admin/projects", "projects", "admin/base", "base", "application"]`
Neat, +1