Создание вложенных ресурсов Ruby on Rails

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

  • Соглашения для обработки таких аспектов, как маршрутизация, stateful данные и управление активами.
  • Архитектуру «модель-представление-контроллер» (MCV), которая позволяет отделить логику приложения, расположенную в моделях, от представлений и маршрутизации информации приложения.

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

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

Требования

  • Локальный компьютер или сервер разработки Ubuntu 18.04. У вас должен быть пользователь с правами администратора (не root) и настроенный брандмауэр ufw. Все инструкции вы найдете в нашем руководстве Начальная настройка сервера Ubuntu 18.04.
  • Node.js и npm на вашей рабочей машине. В мануале мы используем версии Node.js 10.16.3 и npm 6.9.0. Рекомендации по установке вы найдете в мануале Установка Node.js в Ubuntu 18.04.
  • Предварительно установленные инструменты Ruby, rbenv и Rails. Здесь мы используем версии Ruby 2.5.1, rbenv 1.1.2 и Rails 5.2.3. Вам пригодится мануал Установка Ruby on Rails с помощью rbenv в Ubuntu 18.04.
  • База данных SQLite и простое приложение. Все это мы создали в мануале Сборка приложения Ruby on Rails.

1: Скаффолдинг приложения

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

Для начала нужно создать модель Post и связанные с ней ресурсы. Для этого мы можем использовать команду rails generate scaffold, которая предоставит нам модель, миграцию базы данных для изменения схемы БД, контроллер, полный набор представлений для управления стандартными операциями CRUD (Create, Read, Update и Delete) и шаблоны для партиалов, хелперов и тестов. Эти ресурсы нуждаются в дополнительной настройке, но команда scaffold сэкономит много времени и сил, поскольку она сгенерирует структуру, которую можно использовать в качестве старта.

Перейдите в каталог sharkapp нашего проекта Rails, который мы создали в предыдущем мануале:

cd sharkapp

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

rails generate scaffold Post body:text shark:references

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

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

cat db/migrate/20190805132506_create_posts.rb

Вы получите результат:

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

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

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

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

Ассоциация own_to устанавливает связь между моделями, где экземпляр декларируемой модели принадлежит одному экземпляру именованной модели. На примере нашего приложения это означает, что один пост принадлежит одной акуле.

Кроме этого отношения команда rails generate scaffold также создала маршруты и представления для постов (как это было сделано в разделе 3 мануала Сборка приложения Ruby on Rails).

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

2: Определение вложенных маршрутов и ассоциаций в родительской модели

Rails уже установил в модели Post ассоциацию belongs_to  благодаря ключевому слову :references  в команде rails generate scaffold. Но чтобы это отношение функционировало должным образом, нужно также указать ассоциацию has_many в модели Shark. Нам также нужно внести изменения в стандартную маршрутизацию, которую предоставил Rails, и сделать ресурсы post дочерними для ресурсов shark.

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

nano app/models/shark.rb

Добавьте эту строку в файл, чтобы установить отношения между моделями Shark и Post.

class Shark < ApplicationRecord
has_many :posts
validates :name, presence: true, uniqueness: true
validates :facts, presence: true
end

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

Для этого добавьте следующий код в файл:

class Shark < ApplicationRecord
has_many :posts , dependent: :destroy
validates :name, presence: true, uniqueness: true
validates :facts, presence: true
end

Сохраните и закройте файл. Если вы используете nano, нажмите Ctrl + X, Y, затем Enter.

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

nano config/routes.rb

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

Rails.application.routes.draw do
resources :posts
resources :sharks
root 'sharks#index'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

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

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

Rails.application.routes.draw do
resources :sharks do
resources :posts
end
root 'sharks#index'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

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

Теперь нужно обновить контроллер posts.

3: Обновление контроллера posts

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

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

nano app/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
# Use callbacks to share common setup or constraints between actions.
def set_post
@post = Post.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def post_params
params.require(:post).permit(:body, :shark_id)
end
end

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

Для изменений мы будем использовать 2 вещи:

  • Методы, которые стали доступны после добавления ассоциаций own_to и has_many в модели. Например, у нас есть доступ к методу build благодаря ассоциации has_many, которую мы определили в модели Shark. Этот метод позволит создать коллекцию объектов post, связанных с конкретным объектом 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. Имея эту переменную в файле, мы сможем связать посты с определенной акулой в других методах.

Перед методами private внизу файла добавьте следующий метод:

. . .
private
def get_shark

@shark = Shark.find(params[:shark_id])


end

# Use callbacks to share common setup or constraints between actions.
. . .

Затем в начале файла добавьте фильтр (поместите его перед существующим фильтром):

class PostsController < ApplicationController
before_action :get_shark

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

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

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

. . .
def index
@posts = @shark.posts
end
. . .

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

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

. . .
def new
@post = @shark.posts.build
end
. . .

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

Далее мы рассмотрим метод, который наиболее тесно связан с new – это метод create. Он делает две вещи: создает новый экземпляр post на основе параметров, введенных пользователями в форму new, а затем сохраняет этот экземпляр (если ошибок нет) и использует хелпера маршрутизации, чтобы перенаправить пользователей туда, где они смогут видеть новый пост. В случае ошибки он снова отображает шаблон new.

Обновите метод create, чтобы он выглядел так:

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

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

Посмотрите на фильтры в верхней части файла. Второй, автоматически сгенерированный фильтр before_action ответит на этот вопрос:

class PostsController < ApplicationController
before_action :get_shark
before_action :set_post, only: [:show, :edit, :update, :destroy]
. . .

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

. . .
private
. . .
def set_post
@post = Post.find(params[:id])
end
. . .

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

Обновите set_post так:

. . .
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 для выбранной акулы:

. . .
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) в случае успешного обновления:

. . .
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
. . .

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

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
# Use callbacks to share common setup or constraints between actions.
def set_post
@post = @shark.posts.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def post_params
params.require(:post).permit(:body, :shark_id)
end
end

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

4: Обновление видов

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

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

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

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

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

<%= form_with(model: [@shark, post], local: true) do |form| %>
. . .

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

С учетом правок файл form  будет выглядеть так:

<%= 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, которое показывает посты, связанные с определенной акулой.

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

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

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

В теле таблицы внесите следующие обновления.

Во-первых, измените post.shark на post.shark.name, чтобы таблица включала поле для названия акулы (вместо идентифицирующей информации о самом объекте):

. . .
<tbody>
<% @posts.each do |post| %>
<tr>
<td><%= post.body %></td>
<td><%= post.shark.name %></td>
. . .

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

. . .
<tbody>
<% @posts.each do |post| %>
<tr>
<td><%= post.body %></td>
<td><%= post.shark.name %></td>
<td><%= link_to 'Show Shark', [@shark] %></td>

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

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

. . .
<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>

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

. . .
<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>

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

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

В результате файл будет выглядеть так:

<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) %>

Сохраните и закройте файл, когда закончите редактирование.

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

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

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

Ссылка link_to в конце файла должна использовать хелпер shark_posts_path(@shark):

. . .
<%= link_to 'Back', shark_posts_path(@shark) %>

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

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

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

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

. . .
<%= link_to 'Show', [@shark, @post] %> |
<%= link_to 'Back', shark_posts_path(@shark) %>

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

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

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

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

. . .
<%= link_to 'Edit', edit_shark_post_path(@shark, @post) %> |
<%= link_to 'Back', shark_posts_path(@shark) %>

Сохраните и закройте файл, когда закончите.

Теперь осталось только обновить представление show для sharks, чтобы посты отображались отдельно. Откройте этот файл:

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

Здесь нужно добавить раздел Posts в форму и ссылку Add Post в конце файла.

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

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

. . .
<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) %> |
. . .

Затем добавьте новый редирект, чтобы позволить пользователям добавлять новые посты.

. . .
<%= link_to 'Edit', edit_shark_path(@shark) %> |
<%= link_to 'Add Post', shark_posts_path(@shark) %> |
<%= link_to 'Back', sharks_path %>

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

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

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

В разделе 5 этого мануала мы добавили проверки в модель Shark, чтобы обеспечить однородность и последовательность данных в БД sharks. Сделаем то же самое для БД posts.

Откройте файл модели Post.

nano app/models/post.rb

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

class Post < ApplicationRecord
belongs_to :shark
validates :body, presence: true, uniqueness: true
end

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

Теперь можно запустить миграцию и протестировать приложение.

Сначала запустите миграцию:

rails db:migrate

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

rails s

Если вы работаете на удаленной машине, введите:

rails s --binding=your_server_ip

Перейдите в корневой каталог приложения:

http://localhost:3000
http://your_server_ip:3000

В предыдущем мануале мы добавили и отредактировали запись Great White shark. Если вы не добавляли больше записей, посадочная страница приложения будет выглядеть следующим образом:

Sharks
Name         Facts
Great White  Large  Show  Edit  Destroy
New Shark

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

Нажмите Add Post  под заголовком Posts. Это приведет вас к представлению index, где у вас будет возможность выбрать New Post.

Поскольку в разделе 6 предыдущего мануала мы установили механизмы аутентификации, приложение может попросить пройти аутентификацию, указав имя пользователя и пароль (если вы находитесь в новой сессии).

Нажмите New Post, и вы получите шаблон new.

New Post
Body
Create Post
Back

В поле Body  введите «These sharks are scary!»

Нажмите Create Post. Вы будете перенаправлены в представление index, где хранятся все посты для этой акулы:

The post was successfully created.
Posts
Body                      Shark
These sharks are scary!   Great White  Show Shark  Edit Post  Destroy Post
New Post

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

В представлении index  нажмите New Post. В поле Body новой формы попробуйте снова ввести «These sharks are scary!»

Нажмите Create Post. Вы увидите следующую ошибку:

1 error prohibited this post from being saved:
Body has already been taken

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

Чтобы убедиться, что вторая проверка работает, снова нажмите New Post. Оставьте тело поста пустым и нажмите Create Post. Вы увидите следующую ошибку:

1 error prohibited this post from being saved:
Body can’t be blank

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

Заключение

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

Tags: , , ,