Rails: ネストされた名前空間により部分への奇妙なパスが生成され、MissingTemplate エラーが発生する

概要

Book という名前の ActiveRecord モデルと Book::Author という名前のモデルがあります。著者は Book::Authorship モデル (1 対多の関連付け) を通じて多数の本を持っています。

オフトピック:

私のアプリでは、管理者のみが書籍と著者を作成/更新/削除できます。そこで、コントローラー用に Admin という名前空間を作成し、管理者専用にビューを作成しました。

Routes.rb ファイルには次のものがあります。

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

次に、Admin::Books::AuthorsController コントローラーを作成しました。

ビューのパスは同じパス パターンに従うことが予想されます。

残念ながら、そうではありません。インデックス ページは機能しますが、_author.html.erb 部分が見つかりません。

/app/views/admin/books/authors/_index.html.erb で著者のリストをレンダリングしようとしている方法は次のとおりです。

  <% @books_authors.each do |book_author| %>
    <%= render book_author %>
  <% end %>

これにより、次のエラーが発生します。

途中に 2 冊の「本」があります…しかしなぜでしょうか?

上記のコードは、テンプレートへの明示的なパスがある場合にのみ機能します。

  <% @books_authors.each do |book_author| %>
    <%= render "admin/books/authors/author", book_author: book_author %>
  <% end %>

これは構成パラダイムに関する慣例を破るので、私は好きではありません。 Admin::Books:: 名前空間の下のすべてのビューに手動でパスを入力したくありません。

明示的なパスを使用せずに目的の機能を実現する方法を探しています。 Rails が部分を検索するときにパスに「books」を 2 回含めないようにするにはどうすればよいでしょうか?

解決策

コントローラーの名前空間は Admin::Books で、モデルは Books::Author なので、両方とも 2 つの「books」パスが得られます。ロジックは大まかに次のようになります。

[
  File.dirname(Admin::Books::AuthorsController.new.lookup_context.prefixes.first),
  Books::Author.new.to_partial_path
].join("/")

#=> "admin/books/books/authors/author"

lookup_context は変更できるものです (おそらく良いアイデアではありません)。

# app/controllers/admin/books/authors_controller.rb

def index
  lookup_context.prefixes = ["admin/books", "application"]
  @books_authors = Books::Author.all
end

もう 1 つの方法は、Books 名前空間の 1 つを削除することです。

# 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

モデルをフラットに保つことは、名前空間がなくても問題なく、規則に従う一般的な方法です。

BookAuthor
PostAuthor