Зарегистрируйтесь, чтобы продолжить обучение

Вложенные ресурсы Ruby On Rails

Программа урока

  • Работа со вложенными ресурсами (limits, shallow nesting)
  • Организация контроллеров, пространство имен (namespace, scope)
  • Кастомные маршруты (member, collection)

Вложенные ресурсы

bin/rails g scaffold book title:string
bin/rails g scaffold page body:text book:references
bin/rails db:migrate

models/book.rb

class Book < ApplicationRecord
  has_many :pages
end

models/page.rb

class Page < ApplicationRecord
  belongs_to :book
end

config/routes.rb

Rails.application.routes.draw do
  root 'books#index'
  # родительский ID передаётся из пути
  # GET books/:book_id/pages
  # GET books/:book_id/pages/new
  # POST books/:book_id/pages
  resources :books, except: [:index] do
    resources :pages, only: [:index, :new, :create]
  end

  resources :books, except: [:index], shallow: true do
    resources :pages, only: [:show, :edit, :update, :destroy]
  end
end

controllers/page_controller.rb

class PagesController < ApplicationController
  before_action :set_book, only: %i[ index new create ]
  before_action :set_page, only: %i[ show edit update destroy ]

  # GET /book/1/pages
  def index
    @pages = @book.pages
  end

  # GET /pages/1
  def show
  end

  # GET book/1//pages/new
  def new
    @page = @book.pages.build
  end

  # GET /pages/1/edit
  def edit
  end

  # POST /book/1/pages
  def create
    @page = @book.pages.build(page_params)

    if @page.save
      redirect_to book_path(@book), notice: "Page was successfully created."
    else
      render :new, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /pages/1
  def update
    if @page.update(page_params)
      redirect_to book_path(@page.book), notice: "Page was successfully updated."
    else
      render :edit, status: :unprocessable_entity
    end
  end

  # DELETE /pages/1
  def destroy
    @page.destroy
    format.html { redirect_to pages_url, notice: "Page was successfully destroyed." }
  end

  private

  def set_book
    @book = Book.find(params[:book_id])
  end

  def set_page
    @page = Page.find(params[:id])
  end

  def page_params
    params.require(:page).permit(:body, :book_id)
  end
end

views/pages/show.html.erb

<p id="notice"><%= notice %></p>

<p>
  <strong>Title:</strong>
  <%= @book.title %>
</p>

<%= link_to 'Add new page', new_book_page_path(@book) %> |
<%= link_to 'Edit', edit_book_path(@book) %> |
<%= link_to 'Back', root_path %>


<h1>Pages</h1>

<table>
  <thead>
    <tr>
      <th>Body</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @book.pages.each do |page| %>
      <tr>
        <td><%= page.body %></td>
        <td><%= link_to 'Edit', edit_page_path(page) %></td>
        <td><%= link_to 'Destroy', page, data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

Организация контроллеров

controllers/web/application_controller.rb

# Контроллер модуля наследуется от базового контроллера
class Web::ApplicationController < ApplicationController
end

controllers/web/books_controller.rb

class Web::BooksController < Web::ApplicationController
  ...
end
# module Web
#   class BooksController < ApplicationController
#   ...
#   end
# end

controllers/web/pages_controller.rb

class Web::PagesController < Web::ApplicationController
  ...
end
# module Web
#   class PagesController < ApplicationController
#   ...
#   end
# end

config/routes.rb

Rails.application.routes.draw do
  # Контроллеры и вьюхи должны находиться в модуле Web
  # Задаётся только модуль, имена роутов и пути не изменяются
  scope module: :web do
    root 'books#index'
    resources :books, except: [:index] do
      resources :pages, only: [:index, :new, :create]
    end

    resources :books, except: [:index], shallow: true do
      resources :pages, only: [:show, :edit, :update, :destroy]
    end
  end
end

Создание нестандартных маршрутов

Rails.application.routes.draw do
  scope module: :web do
    root 'books#index'
    resources :books, except: [:index] do
      resources :pages, only: [:index, :new, :create]
      # добавляется метод для каждого экземпляра ресурса
      post :publish
    end
  end
end

# bin/rails routes -g publish
#       Prefix Verb   URI Pattern                       Controller#Action
# book_publish POST   /books/:book_id/publish(.:format) web/books#publish


Rails.application.routes.draw do
  scope module: :web do
    root 'books#index'
    resources :books, except: [:index] do
      # добавляется метод для каждого экземпляра ресурса
      collection do
        # Метод один для всех данных ресурсов
        post :publish
      end
    end
  end
end

# bin/rails routes -g publish
# =>        Prefix Verb   URI Pattern               Controller#Action
# => publish_books POST   /books/publish(.:format)  web/books#publish

Rails.application.routes.draw do
  scope module: :web do
    root 'books#index'
    resources :books, except: [:index] do
      # добавляется метод для каждого экземпляра ресурса
      member do
        post :publish
      end
    end
  end
end

# bin/rails routes -g publish
# =>       Prefix Verb URI Pattern                  Controller#Action
# => publish_book POST /books/:id/publish(.:format) web/books#publish

Самостоятельная работа

  • Если у вас уже есть Rails-проект, то подумайте, есть ли в нем зависимые друг от друга ресурсы. Если да, сделайте рефакторинг
  • Если проекта нет, то создайте его. Повторите примеры из теории.

Подумайте, какие еще ресурсы могут быть зависимы? Попробуйте их реализовать.


Дополнительные материалы

  1. Nested Resources routing
  2. The Types of Associations
  3. Метод scope

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff