高级Rails模式:围绕嵌套文档、章节、页面层次结构组织视图



Rails显示深度嵌套层次结构视图的方式是什么?这似乎是一个简单的问题,但请耐心等待,也许有人会理解(并希望澄清)我的困惑。

我的应用程序是一个简单的层次结构(为了本次讨论的目的,更简化了):

  • 在顶部,我有一个文档索引
  • 单击其中一个,即可查看有关文档的更多详细信息,包括其章节的列表(索引)
  • 单击该列表中的一章,并获得带有上一个&下一个链接和页边距中类似的章节列表,突出显示当前章节

很简单,但我的问题是通往的轨道是什么

  1. 嵌套资源,这样它们就不会太深
  2. 构建视图

我之所以问一些看似如此基本的问题,是因为从第一次点击开始,我查看的是"文档"(document#show),还有它包含的章节(chapters#index)。那么,应该在两个中的哪一个中构建视图呢?

下一次点击时,我会看到一章的开头。这可能是chapters#showpages#index动作。不过,可以想象的是,我想它也可以(也许应该)由pages#show处理。

想法?我是不是想得太多了?(可能)

路线

您可以这样设置路由,以利用Rails内置的嵌套资源:

resources :documents, :only => [:index, :show] do
resources :chapters, :only => :show do
resources :pages, :only => :show
end
end

这大致相当于设置这些自定义路线:

get '/documents' => 'documents#index',
:as => 'documents'
get '/documents/:id' => 'documents#show',
:as => 'document'
get '/documents/:document_id/chapters/:id' => 'chapters#show',
:as => 'document_chapter'
get '/documents/:document_id/chapters/:chapter_id/pages/:id' => 'pages#show',
:as => 'document_chapter_page'

如果URL比您希望的更长、更麻烦,或者您想使用自定义参数(例如,通过编号而不是ID识别页面),您可以随时编写自定义路由:

resources :documents, :only => [:index, :show]
get '/documents/:document_id/:id' => 'chapters#show',
:as => 'document_chapter'
get '/documents/:document_id/:chapter_id/:page' => 'pages#show',
:as => 'document_chapter_page'

有关详细信息,请参阅布线指南。

控制器

你不希望用户能够直接访问某一章,相反,他们应该看到该章的第一页。然而,可以说,章节URL在没有页码的情况下工作是有用的(尤其是如果页码指的是文档中的位置,而不是章节中的位置)。

因此,您可以从章节控制器的show操作重定向到第一页:

class ChaptersController < ApplicationController
def show
chapter = Chapter.find(params[:id])
redirect_to [chapter.document, chapter, chapter.pages.first]
end
end

视图

有两种方法可以在不同的视图之间共享章节列表:

  1. 使用Rails内置的对渲染模型对象集合的支持。在views/documents/show.html.erbviews/chapters/show.html.erb中,您可以包含以下内容(我假设每个操作都设置一个@document变量):

    <ol>
    <%= render @document.chapters %>
    </ol>
    

    Rails将查找一个名为views/chapters/_chapter.html.erb的局部视图,并为每个章节呈现它。它可能看起来像这样:

    <li><%= link_to chapter, [chapter.document, chapter] %></li>
    
  2. 在单个部分中共享整个列表。例如,您可以将以下内容添加到views/documents/show.html.erbviews/chapters/show.html.erb

    <%= render 'chapters/list', :chapters => @document.chapters %>
    

    并创建一个名为views/chapters/_list.html.erb:的局部视图

    <ol>
    <% chapters.each do |chapter| %>
    <li><%= link_to chapter, [chapter.document, chapter] %></li>
    <% end %>
    </ol>
    

有关详细信息,请参见手册的"布局和渲染"部分中关于使用Partials的部分。

我不止一次读到嵌套不应该被滥用,2个级别显然很好,如果有意义的话可以是3个级别,但任何超过3个级别的东西,你可能需要重新思考。

您可以使用三个级别的嵌套,即

/docs/:id/:charge/:page

但做这种也可能是有意义的

/docs/:id/:page

即直接引用文档的页面,而不关心指定章节

如果章节是一个数字,页面是一个号码,您的路由将如何区分,即

/docs/1001/1

那是1001号文件的第1页吗?还是1001号文件的第一章?

只是在扮演魔鬼的代言人,当你试图深入挖掘时,就会显示出潜在的缺点。

我认为最自然的事情是:

'/docs' => 'docs#index'  # show all docs
'/docs/:id'  => 'docs#show' # show all chapters in a doc
'/docs/:id/chapter/:chpt' => 'docs#show_chapter' # show all pages in a chapter in a doc
'/docs/:id/:page'  => 'docs#show'  # show a page in a doc
the show actions would have:
if params[:page]
# show page specified
else
# show all chapters
end
the order of the routes is important

我建议这样做的原因是,书籍/页面查询语义可能比书籍/章节/页面更常见。

关联将是:

class Doc < ActiveRecord::Base
has_many :pages
has_many :chapters
end

我不会在路由中暴露end_page,我只会加标签?endpage=X'在任何接受:page的路由上,例如在显示操作中:

if params[:page]
if params[:end_page]
# show pages :page through :end_page
else
# show single :page
end
else
# show all chapters
end

最新更新