I have an ActiveRecord model named Book
, and a model named Book::Author
. An author has many books through the Book::Authorship
model (one-to-many association).
Off-topic:
The reason to put the Author model under the Book namespace is that I might want to have another author model (e.g. the Post::Author) which is completely unrelated to the Book::Author. With a namespace I can clearly show the relationship, e.g.:
example.com/books/authors
explicitly states that these are the authors that wrote books (and not posts or anything else).
In my app only administrators can create/update/delete Books and Authors. So I've created a separated namespace Admin
for the controllers and the views for the administrators only.
In the routes.rb
file I have:
Rails.application.routes.draw do
# These are for the regular users:
# only #index and #show actions are defined in the respective controllers:
namespace :books do
resources :authors, only: %i[index show]
end
resources :books, only: %i[index show]
# These are for the admins only:
# all CRUD methods are defined in the respective controllers:
namespace :admin do
namespace :books do
resources :authors
end
resources :books
end
resources :admin
root "books#index"
end
Then I've created the Admin::Books::AuthorsController
controller:
/app/controllers/admin/books/authors_controller.rb
I would expect that the view path follows the same path pattern:
/app/views/admin/books/authors/_index.html.erb
for the index;/app/views/admin/books/authors/_author.html.erb
for the partial.Unfortunately, it doesn't: the index page does work, but it can't find the _author.html.erb
partial.
Here's how I'm trying to render the list of authors in the /app/views/admin/books/authors/_index.html.erb
:
<% @books_authors.each do |book_author| %>
<%= render book_author %>
<% end %>
Which gives the following error:
ActionView::MissingTemplate in Admin::Books::Authors#index
Missing partial admin/books/books/author/_author with {...}.
There are two “books” in the path... but why?
The code above works only with an explicit path to the template:
<% @books_authors.each do |book_author| %>
<%= render "admin/books/authors/author", book_author: book_author %>
<% end %>
Which I don't like, since it breaks the convention over configuration paradigm. I don't want to manually type a path in every view under the Admin::Books::
namespace.
I'm looking for a way to achieve the desired functionality without using an explicit path. How I can tell Rails to not include “books” twice in the path when it's looking for a partial?
You controller is namespaced Admin::Books
and your model is Books::Author
, together you get double "books" path. The logic roughly looks like this:
[
File.dirname(Admin::Books::AuthorsController.new.lookup_context.prefixes.first),
Books::Author.new.to_partial_path
].join("/")
#=> "admin/books/books/authors/author"
lookup_context
is something you could modify (probably not a good idea):
# app/controllers/admin/books/authors_controller.rb
def index
lookup_context.prefixes = ["admin/books", "application"]
@books_authors = Books::Author.all
end
Another way would be to drop one of the Books
namespaces:
# either change your controller
class Admin::AuthorsController < ApplicationController
def index
@books_authors = Books::Author.all
end
end
# or model, what if someone writes a book and a post?
class Admin::Books::AuthorsController < ApplicationController
def index
@books_authors = Author.joins(:books)
end
end
Keeping your models flat is a popular way of sticking to conventions, no namespaces - no problems:
BookAuthor
PostAuthor