Створення движка для блогу з допомогою Phoenix і Elixir / Частина 7. Додаємо коментарі / Новорічний анонс в ув'язненні



Від перекладача: «Elixir і Phoenix — прекрасний приклад того, куди рухається сучасна веб-розробка. Вже зараз ці інструменти надають якісний доступ до технологій реального часу для веб-додатків. Сайти з підвищеною інтерактивністю, багатокористувацькі браузерні ігри, микросервисы — ті напрямки, в яких дані технології послужать хорошу службу. Далі представлений переклад серії з 11 статей, докладно описують аспекти розробки на фреймворку Фенікс здавалося б такий тривіальної речі, як блоговый движок. Але не поспішайте кукситься, буде дійсно цікаво, особливо якщо статті спонукають вас звернути увагу на Еліксир або стати його послідовниками.

У цій частині ми підготуємо фундамент для коментарів, щоб потім оживити їх за допомогою каналів Phoenix.


На даний момент наше додаток засноване на:

  • Elixir: v1.3.1
  • Phoenix: v1.2.0
  • Ecto: v2.0.2
На чому ми зупинилися
Зараз движок підтримує використання невеликого Markdown-редактора для прикраси постів, так що наша виріб стає схожою на повноцінний проект! Однак у нас досі немає ніякого способу отримувати зворотний зв'язок до постів, які пишемо. Хороша новина: додати таку можливість досить просто. Вся робота буде будуватися на тому, що ми вже встигли зробити.

Почнемо з простого. Замість вимоги зареєструватися, будемо створювати нові коментарі в статусі очікування затвердження. Коментарі стануть видні на сторінці посту відразу після перевірки, або при натисканні «Показати непідтверджені».

Додаємо модель коментарів
Почнемо з додавання моделі для коментарів. У коментарів:

  • Автор (рядковий тип)
  • Повідомлення (текстовий тип)
  • Ознака схваленого коментаря (логічний тип, за замовчуванням false)
  • Пост, до якого відноситься коментар (посилання на таблицю з постами)
Нам не потрібен повний набір шаблонів і інші принади, так що скористаємося командою
mix phoenix.gen.model
.

mix phoenix.gen.model Comment comments author:string body:text approved:boolean post_id:references:posts

Потім проведемо міграцію:

mix ecto.migrate

Пов'язуємо коментарі з постами
У файлі
web/models/comment.ex
можна побачити, що коментарі вже пов'язані з посадами, але не вистачає зв'язку у зворотний бік.

Так що додайте визначення схеми «posts» у файлі
web/models/post.ex
наступний код:

has_many :comments, Pxblog.Comment

Після виконання команди
mix test
, все повинно бути по-як і раніше зеленим! Тепер давайте перевіримо зв'язок між постами і коментарями.

Для цього відкрийте файл
test/support/factory.ex
і додайте фабрику Comment. Напишіть цю сходинку вгору файлу, слідом за іншими псевдонімами:

alias Pxblog.Comment

А потім цей код у самий низ:

def comment_factory do
%Comment{
author: "Test User",
body: "This is a sample comment",
approved: false,
post: build (post)
}
end

Відповідно, потрібно створити кілька тестів, щоб створена фабрика принесла користь. Відкрийте файл
test/models/comment_test.exs
і додайте в нього наступний код:

import Pxblog.Factory
# ...
test "creates a comment associated with a post" do
comment = insert(:comment)
assert comment.post_id
end

Запустимо тести знову. Вони повинні залишитися зеленими!

Додавання маршрутів для коментарів
Почнемо з створення основних маршрутів для коментарів. Відкрийте файл
web/router.ex
:

resources "/posts", PostController, only: [] do
resources "/comments", CommentController, only: [:create, :delete, update]
end

Коментарі мають сенс тільки в контексті постів, так що вкладемо їх всередину. При цьому, в інших маршрутах, які ми вже визначили, пости вкладені у користувачів! Не хочеться створювати зайвих маршрутів для постів, тому скористаємося параметром
only: []
. Потім додамо ресурси для коментарів, щоб дати можливість створювати, видаляти і оновлювати їх.
:create
для додавання коментарів неавторизонными користувачами (створюються непідтвердженими).
:delete
— дозволить автору поста і адміністраторам видаляти комменарии, а
update 
— схвалювати їх для показу громадськості.

Додаємо контролери та подання
Тепер, коли наші моделі налаштовані, нам потрібно створити контролер з парою методів. Відображення коментарів буде реалізований через контролер постів, але створення/оновлення/видалення повинно бути реалізовано в їх власному контролері. Почнемо з створення файлу 
web/controllers/comment_controller.ex
:

defmodule Pxblog.CommentController do
use Pxblog.Web, :controller
end

Також створимо подання у файлі web/views/comment_view.ex, як того хоче Phoenix.

defmodule Pxblog.CommentView do
use Pxblog.Web, :view
end

Тепер повернемося назад в контролер і додамо базову структуру з трьох дій:
create
 
update
та 
delete
.

def create(conn, _), do: conn
def update(conn, _), do: conn
def delete(conn, _), do: conn

Потім потрібно створити в новій директорії шаблон форми додавання коментаря, який розмістимо на сторінці показу поста:

$ mkdir web/templates/comment

Дозволяємо користувачам залишати коментарі
Почнемо з створення файлу 
web/templates/comment/form.html.eex
:


<%= form_for @changeset, @action, fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<div class="form-group">
<%= label f, :author, class: "control-label" %>
<%= text_input f, :author, class: "form-control" %>
<%= error_tag f, :author %>
</div>
<div class="form-group">
<%= label f, :body, class: "control-label" %>
<%= textarea f, :body, class: "form-control", id: "body-editor" %>
<%= error_tag f, :body %>
</div>
<div class="form-group">
<%= submit "Submit", class: "btn btn-primary" %>
</div>
<% end %>

Звичайна формочка, тут нема що обговорювати.

Тепер перейдемо до файлу
web/templates/post/show.html.eex
, який додамо зв'язок із цією формою. Зверніть увагу, що в цьому шаблоні ми використовуємо дві змінні 
@changeset
та
@action
. Ми повернемося до цього пізніше в контролері
web/controllers/post_controller.ex
. А зараз продовжимо працювати з шаблоном. Після списку атрибутів посту, додайте наступний рядок:

<%= render Pxblog.CommentView, "form.html", changeset: @comment_changeset, action: post_comment_path(@conn, :create, @post) %>

Нам потрібно послатися на «form.html» подання
CommentView
, так що передамо назва першим аргументом у викликах
render
. Нам потрібно передати туди
@comment_changeset
(який ми поки не визначили, але скоро зробимо це) і
@action
— шлях для надсилання коментарів.

Тепер можемо перейти до файлу
web/controllers/post_controller.ex
і зробити так, щоб все працювало. Змініть функцію show у відповідності з кодом:

def show(conn, %{"id" => id}) do
post = Repo.get!(assoc(conn.assigns[:user], :posts), id)
comment_changeset = post
|> build_assoc(:comments)
|> Pxblog.Comment.changeset()
render(conn, "show.html", post: post, comment_changeset: comment_changeset)
end

Тепер повернемося до CommentController (файл
web/controllers/comment_controller.ex
) і наповнимо вмістом функцію create. Прямо перед функціями додайте наступний код:

alias Pxblog.Comment
alias Pxblog.Post
plug :scrub_params, "comment" when in action [:create, :update]

Включення update виклик scrub_params знадобиться нам пізніше. Тепер перескочимо до функції create розмістимо в ній наступний код:

def create(conn, %{"comment" => comment_params, "post_id" => post_id}) do
post = Repo.get!(Post, post_id) |> Repo.preload([:user, :comments])
changeset = post
|> build_assoc(:comments)
|> Comment.changeset(comment_params)
case Repo.insert(changeset) do
{:ok, _comment} ->
conn
|> put_flash(:info, "Comment created successfully!")
|> redirect(to: user_post_path(conn, :show, post.user, post))
{:error, changeset} ->
render(conn, Pxblog.PostView, "show.html", post: post, user: post.user, comment_changeset: changeset)
end
end

Зараз ми будемо створювати коментар при отриманні параметрів comment_params і ідентифікатора поста post_id, так як вони є обов'язковими. Спочатку забираємо пов'язаний пост (не забудьте предзагрузить користувача та коментарі, так як шаблон скоро почне на них посилатися), на основі якого створимо новий changeset. Для цього починаючи з посади, йдемо ланцюжка в функцію 
build_assoc
для створення пов'язаної схеми, яку визначаємо через атом. У нашому випадку створюється пов'язаний
comment
. Результат разом з comment_params передаємо в функцію Comment.changeset. Інша частина працює стандартно з одним винятком.

Умова помилки ледве більш складне, тому що ми використовуємо рендер іншого подання. Спочатку ми передаємо з'єднання connection, потім пов'язане уявлення View (в нашому випадку
Pxblog.PostView
), шаблон для фонового та всі змінні, що використовуються в шаблоні:
@post
 
@user
, and 
@comment_changeset
. Тепер можна протестувати: якщо відправити коментар з помилками, то ви побачите список прямо на сторінці. Якщо при відправці коментаря не буде ніяких помилок, ви отримаєте синій flash-повідомлення вгорі сторінки Ми робимо успіхи!

Висновок коментарів
Тепер нам потрібно відображати коментарі на сторінці посту. Для цього сформуємо загальний шаблон коментаря, який можна використовувати в будь-яких місцях для різних цілей. Створіть файл 
web/templates/comment/comment.html.eex
 і заповніть його наступним:

<div class="comment">
<div class="row">
<div class="col-xs-4">
<strong><%= @comment.author %></strong>
</div>
<div class="col-xs-4">
<em><%= @comment.inserted_at %></em>
</div>
<div class="col-xs-4 text-right">
<%= unless @comment.approved do %>
<button class="btn btn-xs btn-primary approve">Approve</button>
<% end %>
<button class="btn btn-xs btn-danger delete">Delete</button>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<%= @comment.body %>
</div>
</div>
</div>

Тут все зрозуміло без пояснень. Кнопки схвалити/видалити поки не підключені. Ми будемо вирішувати це питання в наступних частинах. Нам також потрібно змінити контролер для попередньо завантажувати коментарів, і включити в шаблон постів show сам список коментарів. Почнемо з оновлення контролера. Додайте рядок у функцію
show
з файлу 
web/controllers/post_controller.ex
відразу за рядком з отриманням постів:

post = Repo.get!(assoc(conn.assigns[:user], :posts), id)
|> Repo.preload(:comments)

Таким чином ми забезпечимо завантаження коментарів як частм посту. Нарешті, відкрийте файл
web/templates/post/show.html.eex
і додайте розділ шаблону, що відображає коментарі:

<div class="comments">
<h2>Comments</h2>
<%= for comment <- @post.comments do %>
<%= render Pxblog.CommentView, "comment.html", comment: comment %>
<% end %>
</div>

Додаємо тести контролера
Не можна зупинятися, поки деякі умови не покриті тестами. Нам потрібно перевірити функцію create на випадок успіху і неуспіху, так як по кожному з цих шляхів може пройти виконання коду.

Створіть файл
test/controllers/comment_controller_test.exs
і приступимо:

defmodule Pxblog.CommentControllerTest do
use Pxblog.ConnCase
import Pxblog.Factory
@valid_attrs %{author: "Some Person", body: "This is a sample comment"}
@invalid_attrs %{}
setup do
user = insert(:user)
post = insert(:post, user: user)
{:ok, conn: build_conn(), user: user, post: post}
end
test "creates resource and redirects when data is valid", %{conn: conn, post: post} do
conn = post conn, post_comment_path(conn, :create, post), comment: @valid_attrs
assert redirected_to(conn) == user_post_path(conn, :show, post.user, post)
assert Repo.get_by(assoc(post :comments), @valid_attrs)
end
test "does not create resource and renders errors when data is invalid", %{conn: conn, post: post} do
conn = post conn, post_comment_path(conn, :create, post), comment: @invalid_attrs
assert html_response(conn, 200) =~ "Oops, something went wrong"
end
end

Знову скористаємося фабрикою Pxblog.Factory. Ми також встановимо дві змінних модуля
@valid_attrs
 і 
@invalid_attrs
точно так само, як ми робили раніше. Додамо блок setup, всередині якого налаштуємо користувача за замовчуванням і посаду, з яким будемо працювати.

Почнемо з тесту на успішне додавання коментаря. Відправляємо POST-запит на вкладений шлях з дійсними атрибутами і перевіряємо, що як і очікувалося спрацювало перенаправлення, а коментар був доданий до посту.

Тепер зробимо те ж саме, але з недійсними даними, і перевіримо отримали повідомлення «Oops, something went wrong» у вигляді HTML. Готово!



Подальші кроки
Ми заготовили відмінний фундамент для коментарів, який, безумовно, можна продовжувати розвивати. Наприклад, ми досі не маємо можливості схвалювати і видаляти коментарі. У наступних декількох частинах ми ще трохи попрацюємо над поліпшенням коментарів перед тим, як переходити на систему живих коментарів на базі каналів Phoenix.

Висновок від Вуншей
За два місяці, що у нас було в цьому році, вдалося зробити перші кроки у популяризації Еліксиру. Першим ділом ми заснували російськомовне співтовариство Wunsh.ru, для якого перевели на російську мову півтора десятка найцікавіших статей про Еліксир і функціональному програмуванні.

На початку тижня ми оновили сайт і виклали в загальний доступ п'ять статей. Сподіваємося, вони розохотять вас і переконають спробувати мову. Наприклад, написати просте додаток в зимові канікули. Завтра ми розішлемо передплатникам повний набір вийшли статей. підпишіться сьогодні та запрошуйте друзів. Будемо раді такому подарунку!

Наступний крок проекту — написати серйозне введення для новачків, перевести офіційну документацію і докладно відповісти на запитання:

  • З чого почати?
  • Як розгорнути проект?
  • Яким редактором користуватися?
  • Які завдання вирішувати?
Та інші…

Майбутній рік буде наповнений стрімким рухом самої мови. Його впровадженням у російські компанії. Про мову не просто будуть знати, його почнуть масово (наскільки це можливо) використовувати.

Спасибі за те, що заходите в наші матеріали. Якщо ви — користувач, то запрошуйте друзів.

Всіх з наступаючим!
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.