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
endCalling /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
endCalling 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
endand
class Admin::ProjectsController < Admin::BaseController
def index
# do something
end
endThe 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]
endAdding 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"]`
Thanks! :)