Routes
You can set up your routes like this to take advantage of Rails' built in nested resources:
resources :documents, :only => [:index, :show] do
resources :chapters, :only => :show do
resources :pages, :only => :show
end
end
This is roughly equivalent to setting up these custom routes:
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'
If the URLs are a little longer and more cumbersome than you had hoped for, or you want to use custom parameters (e.g. identifying pages by number and not by ID), you can always write custom routes instead:
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'
See the routing guide for more information.
Controllers
You don't want a user to be able to visit a chapter directly, instead they should see the first page of the chapter. However, it's arguably useful for chapter URLs to work without a page number (especially if page numbers refer to the position in a document and not the position in a chapter).
You could therefore redirect from the chapters controller's show
action to the first page:
class ChaptersController < ApplicationController
def show
chapter = Chapter.find(params[:id])
redirect_to [chapter.document, chapter, chapter.pages.first]
end
end
Views
There are two approaches you could take to sharing the list of chapters between different views:
Use Rails' built in support for rendering a collection of model objects. In your views/documents/show.html.erb
and views/chapters/show.html.erb
you can include something like the following (I'm assuming each action sets a @document
variable):
<ol>
<%= render @document.chapters %>
</ol>
Rails will look for a partial view called views/chapters/_chapter.html.erb
, and render it for each of the chapters. It could look like this:
<li><%= link_to chapter, [chapter.document, chapter] %></li>
Share the whole list in a single partial. For example, you could add the following to views/documents/show.html.erb
and views/chapters/show.html.erb
:
<%= render 'chapters/list', :chapters => @document.chapters %>
And create a partial view called views/chapters/_list.html.erb
:
<ol>
<% chapters.each do |chapter| %>
<li><%= link_to chapter, [chapter.document, chapter] %></li>
<% end %>
</ol>
See the section on Using Partials in the Layouts and Rendering guide for more information.