Как создать вложенные ресурсы для приложения Ruby on Rails

Введение

Ruby on Rails – это веб-фреймворк, написанный на Ruby, который предлагает разработчикам мнение по поводу разработки приложений. Работа с Rails дает разработчикам:

  • Конвенции для обработки вещей, таких как маршрутизация, состояние данных и управление ресурсами.
  • A firm grounding in the model-view-controller (MCV) architectural pattern, which separates an application’s logic, located in models, from the presentation and routing of application information.

По мере усложнения ваших приложений Rails, скорее всего, вам придется работать с несколькими моделями, которые представляют бизнес-логику вашего приложения и взаимодействуют с вашей базой данных. Добавление связанных моделей означает установление значимых отношений между ними, что затем влияет на то, как информация передается через контроллеры вашего приложения, и на то, как она захватывается и представляется пользователям через представления.

В этом руководстве вы будете работать с существующим приложением Rails, которое предлагает пользователям факты о акулах. В этом приложении уже есть модель для обработки данных об акулах, но вы добавите вложенный ресурс для постов о отдельных акулах. Это позволит пользователям создавать более широкий объем мыслей и мнений об отдельных акулах.

Предварительные требования

Чтобы следовать этому руководству, вам понадобятся:

  • A local machine or development server running Ubuntu 18.04. Your development machine should have a non-root user with administrative privileges and a firewall configured with ufw. For instructions on how to set this up, see our Initial Server Setup with Ubuntu 18.04 tutorial.
  • Node.js и npm установлены на вашем локальном компьютере или сервере разработки. В этом руководстве используются Node.js версии 10.16.3 и npm версии 6.9.0. Для инструкций по установке Node.js и npm в Ubuntu 18.04 следуйте инструкциям в разделе “Установка с использованием PPA” в Как установить Node.js на Ubuntu 18.04.
  • Ruby, rbenv и Rails установлены на вашем локальном компьютере или сервере разработки, следуя шагам 1-4 в Как установить Ruby on Rails с помощью rbenv на Ubuntu 18.04. В этом руководстве используются Ruby 2.5.1, rbenv 1.1.2 и Rails 5.2.3.
  • Установлен SQLite, и создано базовое приложение с информацией о акулах, следуя указаниям в Как создать приложение на Ruby on Rails.

Шаг 1 — Создание вложенной модели

Наше приложение будет использовать ассоциации Active Record для создания связи между моделями Shark и Post: записи будут принадлежать определенным акулам, и каждая акула может иметь несколько записей. Наши модели Shark и Post будут связаны через ассоциации belongs_to и has_many.

Первым шагом к созданию приложения таким образом будет создание модели Post и связанных ресурсов. Для этого мы можем использовать команду rails generate scaffold, которая предоставит нам модель, миграцию базы данных для изменения схемы базы данных, контроллер, полный набор представлений для управления стандартными операциями Создание, Чтение, Обновление и Удаление (CRUD), а также шаблоны для частичных представлений, помощников и тестов. Нам нужно будет модифицировать эти ресурсы, но использование команды scaffold позволит нам сэкономить время и энергию, так как она генерирует структуру, которую мы можем использовать в качестве отправной точки.

Сначала убедитесь, что вы находитесь в каталоге sharkapp для проекта Rails, который вы создали в предварительных условиях:Создайте свои ресурсы Post с помощью следующей команды:

  1. cd sharkapp

Создайте свои ресурсы Post с помощью следующей команды:

rails generate scaffold Post body:text shark:references

С помощью body:text мы сообщаем Rails о включении поля body в таблицу posts базы данных — таблицу, которая сопоставляется с моделью Post. Мы также включаем ключевое слово :references, которое устанавливает связь между моделями Shark и Post. В частности, это обеспечит добавление внешнего ключа, представляющего каждую запись акулы в базе данных sharks, в базу данных posts.

После выполнения команды вы увидите вывод, подтверждающий ресурсы, которые Rails сгенерировал для приложения. Прежде чем двигаться дальше, вы можете проверить файл миграции базы данных, чтобы посмотреть на связь, которая теперь существует между вашими моделями и таблицами базы данных. Используйте следующую команду, чтобы посмотреть содержимое файла, заменяя временную метку на свою миграцию на то, что показано здесь:

  1. cat db/migrate/20190805132506_create_posts.rb

Вы увидите следующий вывод:

Output
class CreatePosts < ActiveRecord::Migration[5.2] def change create_table :posts do |t| t.text :body t.references :shark, foreign_key: true t.timestamps end end end

Как видите, в таблице есть столбец для внешнего ключа акулы. Этот ключ будет иметь форму model_name_id — в нашем случае, shark_id.

Rails установил связь между моделями и в других местах. Посмотрите на недавно сгенерированную модель Post с помощью следующей команды:

cat app/models/post.rb
Output
class Post < ApplicationRecord belongs_to :shark end

belongs_to ассоциация устанавливает связь между моделями, в которой одиночная инстанция объявляющей модели принадлежит одиночной инстанции названной модели. В случае нашего приложения это означает, что одна публикация принадлежит одной акуле.

Помимо установления этой связи, команда rails generate scaffold также создала маршруты и представления для публикаций, как это было сделано для наших ресурсов акул в Шаге 3 из Как построить приложение Ruby on Rails.

Это хорошее начало, но нам нужно будет настроить дополнительные маршруты и упрочить ассоциацию Active Record для модели Shark, чтобы связь между нашими моделями и маршрутами работала так, как мы хотим.

Шаг 2 — Уточнение вложенных маршрутов и ассоциаций для родительской модели

Чтобы добавить ассоциацию has_many к модели Shark, откройте файл app/models/shark.rb с помощью nano или вашего любимого редактора:

Добавьте следующую строку в файл, чтобы установить связь между акулами и постами:

  1. nano app/models/shark.rb

Одна вещь, над которой стоит подумать здесь, – это то, что происходит с постами после удаления определенной акулы. Вероятно, мы не хотим, чтобы посты, связанные с удаленной акулой, сохранялись в базе данных. Чтобы гарантировать удаление всех постов, связанных с определенной акулой, при удалении этой акулы, мы можем использовать опцию dependent с ассоциацией.

~/sharkapp/app/models/shark.rb
class Shark < ApplicationRecord
  has_many :posts
  validates :name, presence: true, uniqueness: true
  validates :facts, presence: true
end

Добавьте следующий код в файл, чтобы гарантировать, что действие destroy на определенной акуле удаляет все связанные посты:

После того как вы закончите внесение этих изменений, сохраните и закройте файл. Если вы используете nano, вы можете сделать это, нажав CTRL+X, затем Y, и нажав ENTER.

~/sharkapp/app/models/shark.rb
class Shark < ApplicationRecord
  has_many :posts , dependent: :destroy
  validates :name, presence: true, uniqueness: true
  validates :facts, presence: true
end

Затем откройте ваш файл config/routes.rb, чтобы изменить отношение между вашими маршрутами:

В настоящее время файл выглядит так:

  1. nano config/routes.rb

# Для получения дополнительной информации о DSL, доступном в этом файле, см. http://guides.rubyonrails.org/routing.htmlТекущий код устанавливает независимое отношение между нашими маршрутами, когда то, что мы хотим выразить, – это зависимое отношение между акулами и их связанными постами.

~/sharkapp/config/routes.rb
Rails.application.routes.draw do
  resources :posts 
  resources :sharks

  root 'sharks#index'
  # Подробности о DSL, доступной в этом файле, см. на http://guides.rubyonrails.org/routing.html
end

Текущий код устанавливает независимую связь между нашими маршрутами, когда нам хотелось бы выразить зависимую связь между акулами и их связанными постами.

Давайте обновим объявление маршрута, чтобы сделать :sharks родителем :posts. Обновите код в файле, чтобы он выглядел следующим образом:

~/sharkapp/config/routes.rb
Rails.application.routes.draw do
  resources :sharks do
    resources :posts
end
  root 'sharks#index'
  # Подробности о DSL, доступной в этом файле, см. на http://guides.rubyonrails.org/routing.html
end

Сохраните и закройте файл после завершения редактирования.

С внесенными изменениями вы можете перейти к обновлению вашего контроллера posts.

Шаг 3 — Обновление Контроллера Постов

Соединение между нашими моделями дает нам методы, которые мы можем использовать для создания новых экземпляров постов, связанных с определенными акулами. Чтобы использовать эти методы, нам нужно добавить их в наш контроллер постов.

Откройте файл контроллера постов:

  1. nano app/controllers/posts_controller.rb

В настоящее время файл выглядит следующим образом:

~/sharkapp/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :set_post, only: [:show, :edit, :update, :destroy]

  # GET /posts
  # GET /posts.json
  def index
    @posts = Post.all
  end

  # GET /posts/1
  # GET /posts/1.json
  def show
  end

  # GET /posts/new
  def new
    @post = Post.new
  end

  # GET /posts/1/edit
  def edit
  end

  # POST /posts
  # POST /posts.json
  def create
    @post = Post.new(post_params)

    respond_to do |format|
      if @post.save
        format.html { redirect_to @post, notice: 'Post was successfully created.' }
        format.json { render :show, status: :created, location: @post }
      else
        format.html { render :new }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /posts/1
  # PATCH/PUT /posts/1.json
  def update
    respond_to do |format|
      if @post.update(post_params)
        format.html { redirect_to @post, notice: 'Post was successfully updated.' }
        format.json { render :show, status: :ok, location: @post }
      else
        format.html { render :edit }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /posts/1
  # DELETE /posts/1.json
  def destroy
    @post.destroy
    respond_to do |format|
      format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Используйте обратные вызовы для обмена общим настройками или ограничениями между действиями.
    def set_post
      @post = Post.find(params[:id])
    end

    # Никогда не доверяйте параметрам из страшного интернета, разрешайте только белый список через.
    def post_params
      params.require(:post).permit(:body, :shark_id)
    end
end

Как и наш контроллер для акул, методы этого контроллера работают с экземплярами связанного класса Post. Например, метод new создает новый экземпляр класса Post, метод index получает все экземпляры класса, а метод set_post использует find и params для выбора конкретного поста по id. Однако, если мы хотим, чтобы наши экземпляры постов были связаны с определенными экземплярами акул, тогда нам нужно будет изменить этот код, так как класс Post в настоящее время действует как независимая сущность.

Наши модификации будут использовать две вещи:

  • Методы, которые стали доступны нам, когда мы добавили ассоциации belongs_to и has_many к нашим моделям. Конкретно, теперь у нас есть доступ к методу build, благодаря ассоциации has_many, которую мы определили в нашей модели Shark. Этот метод позволит нам создавать коллекцию объектов постов, связанных с определенным объектом акулы, используя внешний ключ shark_id, который существует в нашей базе данных posts.
  • Маршруты и вспомогательные функции маршрутизации, которые стали доступны при создании вложенного маршрута posts. Для полного списка примеров маршрутов, которые становятся доступны при создании вложенных отношений между ресурсами, см. документацию Rails. Пока будет достаточно знать, что для каждой конкретной акулы — скажем, sharks/1 — будет связанный маршрут для постов, связанных с этой акулой: sharks/1/posts. Также будут вспомогательные функции маршрутизации, такие как shark_posts_path(@shark) и edit_sharks_posts_path(@shark), которые относятся к этим вложенным маршрутам.В файле мы начнем с написания метода get_shark, который будет выполняться перед каждым действием в контроллере. Этот метод будет создавать локальную переменную экземпляра @shark, найдя экземпляр акулы по shark_id. С этой переменной, доступной нам в файле, будет возможно связывать посты с определенной акулой в других методах.

В файле мы начнем с написания метода get_shark, который будет выполняться перед каждым действием в контроллере. Этот метод создаст локальную переменную экземпляра @shark, находящую экземпляр акулы по shark_id. С этой переменной, доступной нам в файле, станет возможным связать посты с конкретным экземпляром акулы в других методах.

Выше других private методов в нижней части файла добавьте следующий метод:

~/sharkapp/controllers/posts_controller.rb
. . . 
private
  def get_shark
@shark = Shark.find(params[:shark_id])
end
  # Используйте обратные вызовы для совместного использования общих настроек или ограничений между действиями.
. . . 

Далее добавьте соответствующий фильтр в начало файла, перед существующим фильтром:

~/sharkapp/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :get_shark

Это обеспечит выполнение get_shark перед каждым определенным действием в файле.

Далее вы можете использовать этот экземпляр @shark для переписывания метода index. Вместо того, чтобы захватывать все экземпляры класса Post, мы хотим, чтобы этот метод возвращал все экземпляры постов, связанные с конкретным экземпляром акулы.

Измените метод index так, чтобы он выглядел следующим образом:

~/sharkapp/controllers/posts_controller.rb
. . .
  def index
    @posts = @shark.posts
  end
. . .

Метод new также потребует аналогичной корректировки, поскольку мы хотим, чтобы новый экземпляр поста был связан с конкретным экземпляром акулы. Для достижения этого, мы можем использовать метод build, вместе с нашей локальной переменной экземпляра @shark.

Измените метод new так, чтобы он выглядел следующим образом:

~/sharkapp/controllers/posts_controller.rb
. . . 
  def new
    @post = @shark.posts.build
  end
. . . 

Этот метод создает объект поста, связанный с конкретным экземпляром акулы из метода get_shark.

Обновите метод create следующим образом:

Затем взгляните на метод update. Этот метод использует переменную экземпляра @post, которая не явно устанавливается в самом методе. Откуда берется эта переменная?

~/sharkapp/controllers/posts_controller.rb
  def create
    @post = @shark.posts.build(post_params)

        respond_to do |format|
         if @post.save  
            format.html { redirect_to shark_posts_path(@shark), notice: 'Post was successfully created.' }
            format.json { render :show, status: :created, location: @post }
         else
            format.html { render :new }
            format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

Взгляните на фильтры в верхней части файла. Второй автоматически сгенерированный фильтр before_action дает ответ:

Метод update (как и show, edit и destroy) получает переменную @post из метода set_post. Этот метод, перечисленный под методом get_shark с нашими другими private методами, в настоящее время выглядит следующим образом:

~/sharkapp/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :get_shark
  before_action :set_post, only: [:show, :edit, :update, :destroy]
  . . .

Соответствуя методам, используемым в других частях файла, нам нужно изменить этот метод так, чтобы @post ссылался на конкретный экземпляр в коллекции постов, связанной с определенной акулой. Здесь следует иметь в виду метод build – благодаря связям между нашими моделями и методам (таким как build), которые доступны нам благодаря этим связям, каждый из наших экземпляров поста является частью коллекции объектов, связанных с определенной акулой. Поэтому логично, что при запросе конкретного поста мы будем запрашивать коллекцию постов, связанных с определенной акулой.Обновите set_post, чтобы он выглядел следующим образом:Вместо того чтобы находить конкретный экземпляр всего класса Post по id, мы вместо этого ищем соответствующий id в коллекции постов, связанных с определенной акулой.

~/sharkapp/controllers/posts_controller.rb
. . . 
private
. . . 
  def set_post
    @post = Post.find(params[:id])
  end
. . .

Соответственно методам, которые мы использовали в других частях файла, нам нужно изменить этот метод так, чтобы @post относился к конкретному экземпляру в коллекции постов, связанных с определенным акулой. Держите в уме метод build – благодаря ассоциациям между нашими моделями и методам (как build), доступными для нас благодаря этим ассоциациям, каждый наш экземпляр поста является частью коллекции объектов, связанных с определенной акулой. Поэтому логично, что при запросе определенного поста, мы запрашиваем коллекцию постов, связанных с определенной акулой.

Обновите set_post следующим образом:

~/sharkapp/controllers/posts_controller.rb
. . . 
private
. . . 
  def set_post
    @post = @shark.posts.find(params[:id])
  end
. . .

Вместо поиска определенного экземпляра всего класса Post по id, мы ищем соответствие id в коллекции постов, связанных с определенной акулой.

Обновив этот метод, мы можем рассмотреть методы update и destroy.

Метод update использует переменную экземпляра @post из set_post и использует ее с post_params, которые пользователь ввел в форме edit. В случае успеха мы хотим, чтобы Rails перенаправил пользователя к представлению index постов, связанных с определенной акулой. В случае ошибок Rails снова отрендерит шаблон edit.

В этом случае единственное изменение, которое нам нужно сделать, – это утверждение redirect_to, чтобы обрабатывать успешные обновления. Обновите его, чтобы перенаправить на shark_post_path(@shark), что перенаправит на представление index постов выбранной акулы:

~/sharkapp/controllers/posts_controller.rb
. . . 
  def update
    respond_to do |format|
      if @post.update(post_params)
        format.html { redirect_to shark_post_path(@shark), notice: 'Post was successfully updated.' }
        format.json { render :show, status: :ok, location: @post }
      else
        format.html { render :edit }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end
. . .

Далее мы сделаем аналогичное изменение в методе destroy. Обновите метод redirect_to, чтобы перенаправлять запросы на shark_posts_path(@shark) в случае успеха:

~/sharkapp/controllers/posts_controller.rb
. . . 
  def destroy
    @post.destroy
     respond_to do |format|
      format.html { redirect_to shark_posts_path(@shark), notice: 'Post was successfully destroyed.' }
      format.json { head :no_content }
    end
  end
. . .

Это последнее изменение, которое мы сделаем. Теперь у вас есть файл контроллера постов, который выглядит следующим образом:

~/sharkapp/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :get_shark
  before_action :set_post, only: [:show, :edit, :update, :destroy]

  # GET /posts
  # GET /posts.json
  def index
    @posts = @shark.posts
  end

  # GET /posts/1
  # GET /posts/1.json
  def show
  end

  # GET /posts/new
  def new
    @post = @shark.posts.build
  end

  # GET /posts/1/edit
  def edit
  end

  # POST /posts
  # POST /posts.json
  def create
    @post = @shark.posts.build(post_params)

        respond_to do |format|
         if @post.save  
            format.html { redirect_to shark_posts_path(@shark), notice: 'Post was successfully created.' }
            format.json { render :show, status: :created, location: @post }
         else
            format.html { render :new }
            format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /posts/1
  # PATCH/PUT /posts/1.json
  def update
    respond_to do |format|
      if @post.update(post_params)
        format.html { redirect_to shark_post_path(@shark), notice: 'Post was successfully updated.' }
        format.json { render :show, status: :ok, location: @post }
      else
        format.html { render :edit }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /posts/1
  # DELETE /posts/1.json
  def destroy
    @post.destroy
    respond_to do |format|
      format.html { redirect_to shark_posts_path(@shark), notice: 'Post was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private

   def get_shark
     @shark = Shark.find(params[:shark_id])
   end
    # Использовать обратные вызовы для совместного использования общих установок или ограничений между действиями.
    def set_post
      @post = @shark.posts.find(params[:id])
    end

    # Никогда не доверяйте параметрам от пугающего интернета, разрешайте только белый список.
    def post_params
      params.require(:post).permit(:body, :shark_id)
    end
end

Контроллер управляет тем, как информация передается из шаблонов представлений в базу данных и наоборот. Наш контроллер теперь отражает взаимосвязь между нашими моделями Shark и Post, в которой посты связаны с определенными акулами. Мы можем перейти к изменению самих шаблонов представлений, где пользователи будут вводить и изменять информацию о постах о конкретных акулах.

Шаг 4 — Изменение представлений

Наши изменения шаблонов представлений будут затрагивать шаблоны, связанные с постами, а также изменение представления show для акул, поскольку мы хотим, чтобы пользователи видели посты, связанные с конкретными акулами.

Начнем с основного шаблона для наших постов: частичного form, который используется в нескольких шаблонах постов. Откройте этот форм теперь:

  1. nano app/views/posts/_form.html.erb

Вместо передачи только модели post в помощник формы form_with, мы будем передавать как модель shark, так и модель post, при этом post устанавливается как дочерний ресурс.

Измените первую строку файла, чтобы она отражала отношение между нашими ресурсами акулы и поста:

~/sharkapp/views/posts/_form.html.erb
<%= form_with(model: [@shark, post], local: true) do |form| %>
. . . 

Далее, удалите раздел, в котором перечисляется shark_id связанной акулы, поскольку это не является существенной информацией в представлении.

Законченный форм, включая наши правки в первой строке и без удаленного раздела shark_id, будет выглядеть следующим образом:

~/sharkapp/views/posts/_form.html.erb
<%= form_with(model: [@shark, post], local: true) do |form| %>
  <% if post.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(post.errors.count, "error") %> prohibited this post from being saved:</h2>

      <ul>
      <% post.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :body %>
    <%= form.text_area :body %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

Сохраните и закройте файл после завершения редактирования.

Далее откройте представление index, которое покажет посты, связанные с определенной акулой:

  1. nano app/views/posts/index.html.erb

Затем измените перенаправление Show, чтобы направлять пользователей на вид show для соответствующей акулы, поскольку они скорее всего захотят иметь способ вернуться к исходной акуле. Мы можем воспользоваться переменной экземпляра @shark, которую мы установили здесь в контроллере, поскольку Rails делает переменные экземпляра, созданные в контроллере, доступными для всех видов. Мы также изменим текст ссылки с Show на Show Shark, чтобы пользователи лучше поняли ее функцию.

Обновите эту строку следующим образом:

На следующей строке мы хотим убедиться, что пользователи направляются по правильному вложенному пути, когда они переходят к редактированию сообщения. Это означает, что вместо перехода на posts/post_id/edit пользователи будут направляться на sharks/shark_id/posts/post_id/edit. Для этого мы будем использовать вспомогательный маршрут shark_post_path и наши модели, которые Rails будет рассматривать как URL-адреса. Мы также обновим текст ссылки, чтобы сделать ее функцию более понятной.

Обновите строку Edit, чтобы она выглядела следующим образом:

~/sharkapp/app/views/posts/index.html.erb
. . . 
  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.body %></td>
        <td><%= post.shark.name %></td>
. . . 

Затем добавим аналогичное изменение к ссылке Destroy, обновив ее функцию в строке и добавив наши ресурсы shark и post:

Наконец, в конце формы мы захотим обновить путь New Post, чтобы пользователи переходили по соответствующему вложенному пути, когда захотят создать новый пост. Обновите последнюю строку файла, чтобы использовать вспомогательный маршрут new_shark_post_path(@shark):Законченный файл будет выглядеть так:

~/sharkapp/app/views/posts/index.html.erb
. . . 
  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.body %></td>
        <td><%= post.shark.name %></td>
        <td><%= link_to 'Show Shark', [@shark] %></td>

Сохраните и закройте файл после завершения редактирования.

Другие изменения, которые мы внесем в представления постов, не будут такими обширными, поскольку наши другие представления используют частичное представление form, которое мы уже отредактировали. Однако мы захотим обновить ссылки link_to в других шаблонах постов, чтобы отразить изменения, которые мы внесли в наше частичное представление form.

~/sharkapp/app/views/posts/index.html.erb
. . . 
  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.body %></td>
        <td><%= post.shark.name %></td>
        <td><%= link_to 'Show Shark', [@shark] %></td>
        <td><%= link_to 'Edit Post', edit_shark_post_path(@shark, post) %></td>

Откройте app/views/posts/new.html.erb:

~/sharkapp/app/views/posts/index.html.erb
. . . 
  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.body %></td>
        <td><%= post.shark.name %></td>
        <td><%= link_to 'Show Shark', [@shark] %></td>
        <td><%= link_to 'Edit Post', edit_shark_post_path(@shark, post) %></td>
        <td><%= link_to 'Destroy Post', [@shark, post], method: :delete, data: { confirm: 'Are you sure?' } %></td>

Обновите ссылку link_to внизу файла, чтобы использовать помощник shark_posts_path(@shark):

~/sharkapp/app/views/posts/index.html.erb
. . . 
<%= link_to 'New Post', new_shark_post_path(@shark) %>

Сохраните и закройте файл после завершения этого изменения.

~/sharkapp/app/views/posts/index.html.erb
<p id="notice"><%= notice %></p>

<h1>Posts</h1>

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

  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.body %></td>
        <td><%= post.shark.name %></td>
        <td><%= link_to 'Show Shark', [@shark] %></td>
        <td><%= link_to 'Edit Post', edit_shark_post_path(@shark, post) %></td>
        <td><%= link_to 'Destroy Post', [@shark, post], method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Post', new_shark_post_path(@shark) %>

Затем откройте шаблон edit:

Помимо пути Back, мы обновим Show, чтобы отразить наши вложенные ресурсы. Измените последние две строки файла так:

Сохраните и закройте файл.

  1. nano app/views/posts/new.html.erb

Затем откройте шаблон show:Внесите следующие изменения в пути Edit и Back внизу файла:Сохраните и закройте файл после завершения.В качестве последнего шага мы захотим обновить представление show для наших акул, чтобы посты были видны для отдельных акул. Откройте этот файл сейчас:Наши изменения здесь будут включать добавление раздела Posts в форму и ссылки Add Post внизу файла.

~/sharkapp/app/views/posts/new.html.erb
. . . 
<%= link_to 'Back', shark_posts_path(@shark) %>

Сохраните и закройте файл после завершения внесения этого изменения.

Далее, откройте шаблон edit:

  1. nano app/views/posts/edit.html.erb

Помимо пути Back, мы обновим Show для отражения наших вложенных ресурсов. Измените последние две строки файла, чтобы они выглядели следующим образом:

~/sharkapp/app/views/posts/edit.html.erb
. . . 
<%= link_to 'Show', [@shark, @post] %> |
<%= link_to 'Back', shark_posts_path(@shark) %>

Сохраните и закройте файл.

Далее, откройте шаблон show:

nano app/views/posts/show.html.erb

Внесите следующие правки в пути Edit и Back в нижней части файла:

~/sharkapp/app/views/posts/edit.html.erb
. . .
<%= link_to 'Edit', edit_shark_post_path(@shark, @post) %> |
<%= link_to 'Back', shark_posts_path(@shark) %>

Сохраните и закройте файл после завершения.

В качестве последнего шага, нам нужно обновить представление show для наших акул, чтобы сообщения были видны для отдельных акул. Откройте этот файл сейчас:

  1. nano app/views/sharks/show.html.erb

Наши правки здесь будут включать добавление раздела Posts в форму и ссылку Add Post в нижней части файла.

Под разделом Facts для данной акулы, мы добавим новый раздел, который проходит через каждую запись в коллекции сообщений, связанных с этой акулой, выводя тело каждого сообщения.

Добавьте следующий код под разделом Facts формы и над перенаправлениями в нижней части файла:

~/sharkapp/app/views/sharks/show.html.erb
. . .
<p>
  <strong>Facts:</strong>
  <%= @shark.facts %>
</p>

<h2>Posts</h2>
<% for post in @shark.posts %>
    <ul>
      <li><%= post.body %></li>
  </ul>
<% end %>

<%= link_to 'Edit', edit_shark_path(@shark) %> |
. . . 

Далее, добавьте новое перенаправление, чтобы позволить пользователям добавить новое сообщение для этой конкретной акулы:

~/sharkapp/app/views/sharks/show.html.erb
. . .
<%= link_to 'Edit', edit_shark_path(@shark) %> |
<%= link_to 'Add Post', shark_posts_path(@shark) %> |
<%= link_to 'Back', sharks_path %>

Сохраните и закройте файл после завершения правки.

Теперь вы внести изменения в модели, контроллеры и представления вашего приложения, чтобы гарантировать, что посты всегда связаны с определенным акулой. В качестве последнего шага мы можем добавить некоторые проверки в нашу модель Post, чтобы обеспечить согласованность данных, сохраняемых в базе данных.

Шаг 5 – Добавление проверок и тестирование приложения

В Шаге 5 из Как создать приложение Ruby on Rails, вы добавили проверки в модель Shark для обеспечения единообразия и согласованности данных, которые сохраняются в базе данных sharks. Теперь мы предпримем аналогичный шаг, чтобы обеспечить гарантии для базы данных posts.

Откройте файл, где определена ваша модель Post:

  1. nano app/models/post.rb

Здесь мы хотим убедиться, что посты не пусты и что они не дублируют содержимое, которое могут разместить другие пользователи. Для достижения этого добавьте следующую строку в файл:

~/sharkapp/app/models/post.rb
class Post < ApplicationRecord
  belongs_to :shark
  validates :body, presence: true, uniqueness: true
end

Сохраните и закройте файл после завершения редактирования.

После внесенных изменений вы готовы запустить миграции и протестировать приложение.

Во-первых, запустите ваши миграции:

  1. rails db:migrate

Затем запустите ваш сервер. Если вы работаете локально, вы можете сделать это, запустив:

  1. rails s

Если вы работаете на сервере разработки, запустите следующую команду вместо этого:

  1. rails s --binding=your_server_ip

Перейдите к корню вашего приложения по адресу http://localhost:3000 или http://your_server_ip:3000.

Учебник по начальному проекту Rails провел вас через добавление и редактирование записи Белая акула. Если вы не добавили других акул, главная страница приложения будет выглядеть так:

Нажмите на Показать рядом с именем Белая акула. Это перенесет вас на представление show для этой акулы. Вы увидите имя акулы и ее факты, а также заголовок Посты без содержимого. Давайте добавим пост, чтобы заполнить эту часть формы.

Нажмите на Добавить пост под заголовком Посты. Это приведет вас к представлению index поста, где у вас будет возможность выбрать Новый пост:

Благодаря механизмам аутентификации, которые вы внедрили в Шаге 6 Как создать приложение Ruby on Rails, вас могут попросить аутентифицироваться с использованием имени пользователя и пароля, которые вы создали на этом Шаге, в зависимости от того, создали ли вы новый сеанс или нет.

Нажмите на Новая запись, что приведет вас к вашей шаблону создания записи new:

В поле Текст введите: “Эти акулы страшные!”

Нажмите на Создать запись. Вы будете перенаправлены на индекс представление для всех записей, относящихся к этим акулам:

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

Из представления индекс нажмите на Новая запись. В поле Текст на новой форме попробуйте ввести “Эти акулы страшные!” снова:

Нажмите на Создать запись. Вы увидите следующую ошибку:

Нажмите на Назад, чтобы вернуться на главную страницу записей.

Чтобы протестировать нашу другую валидацию, нажмите на Новая запись снова. Оставьте запись пустой и нажмите Создать запись. Вы увидите следующую ошибку:

С корректно работающими вложенными ресурсами и валидациями, у вас теперь есть работающее приложение Rails, которое вы можете использовать в качестве отправной точки для дальнейшего развития.

Заключение

С вашим приложением Rails вы теперь можете работать над такими вещами, как стилизация и разработка других компонентов фронт-энда. Если вы хотите узнать больше о маршрутизации и вложенных ресурсах, документация Rails – отличное место для начала.

Чтобы узнать больше о интеграции фронт-энд фреймворков с вашим приложением, посмотрите Как настроить проект Ruby on Rails с фронт-эндом React.

Source:
https://www.digitalocean.com/community/tutorials/how-to-create-nested-resources-for-a-ruby-on-rails-application