前回までで、認証機能が加わりユーザーの区別ができるようになりました。
これで誰が発言したかを表示できるようになり、チャットっぽい感じになってきました。
今回は、チャットに必要なコンテキスト周りを作成していきます。
本記事でLiveViewでコメント表示をするための前準備が完了します。
本テーマは7記事構成になっています。
7記事それぞれで取り扱う内容は以下です。
本テーマは以下の方針で記述します。
完成版の画面イメージは以下のようになっています。
以下のようなシナリオを想定します。
これから Board
と Comment
を定義し、関連付けを行っていきます。
以下のような関連を作ります。
まずはチャットボードを作ります。
チャットボードのスキーマを以下のようにします。
メンバー | 型 | 備考 |
---|---|---|
name | string |
|
description | string |
|
user_id | references:users |
|
上記スキーマに対して、コンテキストを Meeting
として作成します。
$ mix phx.gen.html Meeting Board boards name:string description:string user_id:references:users
自動生成されたスキーマに関連付けが行われるように変更します。
schema "boards" do
field :description, :string
field :name, :string
- field :user_id, :id
+ belongs_to :user, LvChat.Accounts.User
timestamps()
end
最終的に以下の様になります。
# /lib/lv_chat/meeting/board.ex
schema "boards" do
field :description, :string
field :name, :string
belongs_to :user, LvChat.Accounts.User
timestamps()
end
マイグレーションファイルは、生成されたものを使用します。
ということで、早速マイグレーションを行います。
$ mix ecto.migrate
認証が必要なページとしてRouterに記述します。
# /lib/lv_chat_web/router.ex
scope "/", LvChatWeb do
pipe_through [:browser, :authenticate_user]
get "/", PageController, :index
resources "/boards", BoardControllerend
チャットボード作成時にユーザーとの関連を追加します。
変更点は以下です。
# /lib/lv_chat_web/meeting.ex
alias LvChat.Accountsdef create_board(%Accounts.User{} = user, attrs \\ %{}) do
%Board{}
|> Board.changeset(attrs) |> Ecto.Changeset.put_assoc(:user, user)
|> Repo.insert()
end
BoardController
において、関数に current_user
が引数で渡ってくると便利です。
ということで、current_user
が引数として渡るように細工します。
以下の記述を BoardController
モジュールの先頭に記述します。
# /lib/lv_chat_web/controllers/board_controller.ex
def action(conn, _) do
args = [conn, conn.params, conn.assigns.current_user]
apply(__MODULE__, action_name(conn), args)
end
これで関数の定義を以下のように書けるようになります。
- def some_func(conn, params) do
+ def some_func(conn, params, current_user) do
これに伴い BoardController
の関数すべてを some_func/2
から some_func/3
に変更する必要があります。
create関数で、さきほど作成したMeeting.create/2
を使用するように変更します。
# /lib/lv_chat_web/controllers/board_controller.ex
def create(conn, %{"board" => board_params}, current_user) do
case Meeting.create_board(current_user, board_params) do
{:ok, board} ->
conn
|> put_flash(:info, "チャットボードの作成に成功しました。")
|> redirect(to: Routes.board_path(conn, :show, board))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
次にコメントを作成します。
コメントのスキーマを以下のようにします。
メンバー | 型 | 備考 |
---|---|---|
body | text |
|
user_id | references:users |
|
board_id | references:boards |
|
以下のコマンドでスキーマを作成します。
$ mix phx.gen.schema Meeting.Comment comments body:text user_id:references:users board_id:references:boards
自動生成されたスキーマに関連付けが行われるように変更します。
- field :user_id, :id
- field :board_id, :id
+ belongs_to :user, LvChat.Accounts.User
+ belongs_to :board, LvChat.Meeting.Board
changesetでデフォルト引数を指定します。
- def changeset(comment, attrs) do
+ def changeset(comment, attrs \\ %{}) do
最終的には以下のようになります。
# /lib/lv_chat/meeting/comment.ex
defmodule LvChat.Meeting.Comment do
use Ecto.Schema
import Ecto.Changeset
schema "comments" do
field :body, :string
belongs_to :user, LvChat.Accounts.User belongs_to :board, LvChat.Meeting.Board
timestamps()
end
@doc false
def changeset(comment, attrs \\ %{}) do comment
|> cast(attrs, [:body])
|> validate_required([:body])
end
end
boards
スキーマに対し、has_many
を追加します。
# /lib/lv_chat/meeting/board.ex
schema "boards" do
field :description, :string
field :name, :string
belongs_to :user, LvChat.Accounts.User
has_many :comments, LvChat.Meeting.Comment
timestamps()
end
コメント関連の関数を追加します。
alias LvChat.Meeting.Comment
def change_comment() do
change_comment(%Comment{})
end
def change_comment(%Comment{} = comment, params \\ %{}) do
Comment.changeset(comment, params)
end
def comment_to_board(%Accounts.User{id: user_id}, board_id, attrs) do
%Comment{board_id: board_id, user_id: user_id}
|> Comment.changeset(attrs)
|> Repo.insert()
end
def list_comments(%Board{} = board) do
Repo.all(
from c in Ecto.assoc(board, :comments),
order_by: [asc: c.id],
limit: 500,
preload: [:user]
)
end
必要な変更が完了したので、マイグレーションを行います。
$ mix ecto.migrate
これでデータ関連の定義が終わりました。
コンテキスト周りが完成しました。
いよいよチャットのメインの部分を作成していきます。