如何构造控制器以匹配嵌套资源

时间:2014-02-27 19:39:19

标签: ruby-on-rails routing

就嵌套资源而言,我的应用对上下文变得非常敏感。

例如,假设我的应用程序是图书管理员应用程序,我可能会有这样的路由:

resources :books
resources :libraries do
  resources :books
end

我希望如此,如果您访问/books,您会看到与您相关的图书,登录用户。无论是您已经签出的书籍,还是您喜欢的书籍或者您拥有的书籍。

但是当您访问/libraries/:id/books时,您应该会看到与该图书馆相关的图书。

现在,在控制器中这很容易做到:

def index
  if params[:library_id]
    @library = Library.find(params[:library_id])
    @books = @library.books
  else
    @books = current_user.books
  end
end

然而,这个模式在我的应用程序中重复了几次(想想如果我想将书籍嵌套在作者或出版商之下!)我的控制器会变得非常复杂。如果我想为每个上下文提供不同的视图,那就更难了。

所以,我想要两本书控制器。一个在根命名空间中,一个在库下命名。

没关系。我已经构建了BooksControllerLibrary::BooksController。但是,我遇到了设置合理路线的问题。

我首先想到我可以指定命名空间:

resources :books
resources :libraries do
  namespace :library do
    resources :books
  end
end

但这打破了现有路线,看起来像/libraries/:id/library/books

所以,我尝试将path: false传递给命名空间,该命名空间修复了路由,但是使命名路由和多态路由非常麻烦。

我希望为了访问/libraries/123/books,我可以这样做:

link_to "See books", [@library, :books] # -or- 
link_to "See books", library_books_path(@library)

但是,由于我们添加了命名空间,现在路由非常庞大:

link_to "See books", [@library, :library, :books] # -or- 
link_to "See books", library_library_books_path(@library)

那么,是否有更传统的方法来构建对嵌套资源有意义的控制器?有没有更好的方法来构建我的路线?

更新

通过将as: false添加到名称空间声明中,我得到了预期的结果,如下所示:

resources :books
resources :libraries do
  namespace :library, path: false, as: false do
    resources :books
  end
end

H-man指出你可以在每个资源上指定一个控制器。然而,由于大量路线的管理可能失控,这种感觉并不正确。

所以这两个解决方案都在工作,但是有更传统的方法可以解决这个问题吗?

更新#2

我已经开始使用来自#defaults的{​​{1}},它允许您直接将参数发送到ActionDispatch::Routing::Mapper::Scoping,而不需要其他一些干扰逻辑。我更喜欢这个,因为我没有设置底片,而是设置正面配置:

#scope

然而,如果我遵循良好的惯例,问题仍然存在......这仍然感觉不是100%正确。

2 个答案:

答案 0 :(得分:1)

如果要为嵌套路径指定不同的控制器,可以始终执行以下操作:

resources :books
resources :libraries do
  resources :books, controller: 'libraries/books'
end

这样,您可以拥有两个控制器:

  • 应用程序/控制器/ books_controller.rb
  • 应用程序/控制器/库/ books_controller.rb

在这种情况下我通常做的是让每个控制器控制器处理范围,并将共享功能放在一个问题中。即:

应用程序/控制器/ books_controller.rb

class BooksController < ApplicationController
  include BooksControllerConcern

private
  def book_scope
    current_user.books
  end
end

应用程序/控制器/库/ books_controller.rb

class Libraries::BooksController < ApplicationController
  include BooksControllerConcern

private
  def book_scope
    Book.find_by(library_id: params[:library_id])
  end
end

关注:

module BooksControllerConcern
  extend ActiveSupport::Concern

  def index
    @books = book_scope.page(params[:page])
  end

  # .. other shared actions
end

您的路线将如下所示:

  • [:books]或books_path =&gt; /书籍
  • [@ library,:books]或library_books_path(@library)=&gt; /库/:ID /书籍

答案 1 :(得分:1)

尝试使用范围模块:

resources :books

scope module: :libraries do
  resources :libraries do
    resources :books
  end
end

然后你应该有这些类型的帮助程序来获取索引操作(例如):

books_path == [:books]
library_books_path(@library) == [@library, :books]

我对libraries资源是否应该位于作用域模块下感到遗憾,但大多数情况下这样做是有意义的。假装你想要把那个块拿出来并稍后放入一个宝石(即使那不是你的计划)。这有助于绘制每个范围模块中的内容。