やること
前回に引き続き、デモアプリを作っていく。
今回は記事に対するコメント機能を追加する。
コメントモデルを追加する
コメントのスキーマはこんな感じ
commnets
id: int,
post_id: int,
name: string(255),
content: text,
inserted_at: datetime,
updated_at: datetime
コメントの表示及び投稿フォームは記事の詳細ページに配置する予定なので、今回はモデルのみを追加。
$ mix phoenix.gen.model Comment comments post_id:references:posts name:string content:string
* creating priv/repo/migrations/20160209074344_create_comment.exs
* creating web/models/comment.ex
* creating test/models/comment_test.exs
Remember to update your repository by running migrations:
$ mix ecto.migrate
$ mix ecto.migrate
Compiled web/models/comment.ex
Generated phoenix_sample app
16:52:27.852 [info] == Running PhoenixSample.Repo.Migrations.CreateComment.change/0 forward
16:52:27.853 [info] create table comments
16:52:27.861 [info] create index comments_post_id_index
16:52:27.898 [info] == Migrated in 0.3s
$
モデル同士の関連を定義する場合は、作成時の型としてreferencesを使う。
生成された、web/models/comment.exを確認してみると
schema "comments" do
field :name, :string
field :content, :string
belongs_to :post, PhoenixSample.Post
timestamps
end
と親に当たる記事に対する参照が定義されていることが確認できる。
コメント表示機能の追加
Postモデルの変更
コメントから記事への参照は定義されているが、逆方向の参照が定義されていないので追加する。
web/models/post.exを以下のように変更
schema "posts" do
field :title, :string
field :content, :string
# add reference to comments
has_many :comments, PhoenixSample.Comment
timestamps
end
テンプレートの変更
コメントは記事の詳細ページにて表示するので、web/templates/post/show.html.eexの最後に以下の記述を追加
<h3>Comments</h3>
<ul>
<%= for comment <- @post.comments do %>
<li><%= comment.name %>: <%= comment.content %> </li>
<%= end %>
</ul>
画面で確認
コメント投稿機能はまだ無いので、DBに直接コメントレコードを追加して、記事詳細ページを開いてみると…
Ecto.Association.NotLoadedで検索してみると、ロードしたモデルの関連先のモデルもビューで使用したいときは、その関連先のモデルもコントローラで明示的にロードしてやる必要があるらしい(はっきりとした情報でもないのですが…)
なので、コントローラ(web/controllers/post_controller.ex)の33行目付近に、commentsをロードする記述を追加
def show(conn, %{"id" => id}) do
post = Repo.get!(Post, id) |> Repo.preload(:comments) # add comment preload
render(conn, "show.html", post: post)
end
今度は無事成功。
コメント投稿機能の追加
コメント投稿機能を追加する。
コメント投稿フォームは、記事詳細ページに配置し、投稿後は同じ記事詳細ページヘ遷移する。
コントローラの修正
コメントは、独立したリソースではなく記事に従属するリソースなので、コメント追加のためのメソッドもPostコントローラに追加することにする。
web/controllers/post_controller.exにAliasを追加して
alias PhoenixSample.Comment
以下のようなメソッドを追加
def add_comment(conn, %{"id" => id, "comment" => post_params}) do
post = Repo.get!(Post, id)
changeset = Comment.changeset(%Comment{post_id: post.id}, post_params)
case Repo.insert(changeset) do
{:ok, _post} ->
conn
|> put_flash(:info, "Comment create successfully.")
|> redirect(to: post_path(conn, :show, post))
{:error, changeset} ->
conn
|> put_flash(:info, "Error occured.")
|> redirect(to: post_path(conn, :show, post))
end
end
ルートの追加
コメントの投稿には
POST /posts/:id/comment
というルートを割り当てたい。
web/router.exの21行目付近に新しいルートを追加。
scope "/", PhoenixSample do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
resources "/posts", PostController
post "/posts/:id/comments", PostController, :add_comment # add comment route
end
確認のために、ルート一覧を確認
$ mix phoenix.routes
page_path GET / PhoenixSample.PageController :index
post_path GET /posts PhoenixSample.PostController :index
post_path GET /posts/:id/edit PhoenixSample.PostController :edit
post_path GET /posts/new PhoenixSample.PostController :new
post_path GET /posts/:id PhoenixSample.PostController :show
post_path POST /posts PhoenixSample.PostController :create
post_path PATCH /posts/:id PhoenixSample.PostController :update
PUT /posts/:id PhoenixSample.PostController :update
post_path DELETE /posts/:id PhoenixSample.PostController :delete
post_path POST /posts/:id/comments PhoenixSample.PostController :add_comment # <- 追加されている
テンプレートの修正
テンプレートにコメント投稿フォームを追加する。
まず、newメソッドを参考に、showメソッドを以下のように修正する。
def show(conn, %{"id" => id}) do
post = Repo.get!(Post, id) |> Repo.preload(:comments)
comment_changeset = Comment.changeset(%Comment{})
render(conn, "show.html", post: post, comment_changeset: comment_changeset)
end
そして、web/templates/post/show.html.eexの最後に以下のような記述を追加。
<%= form_for @comment_changeset, post_path(@conn, :add_comment, @post), fn f -> %>
<div class="form-group">
<span>名前: <%= text_input f, :name, size: 10 %></span>
<span><%= text_input f, :content, size: 50 %></span>
<%= error_tag f, :name %>
<%= error_tag f, :content %>
</div>
<div class="form-group">
<%= submit "コメントする", class: "btn btn-primary" %>
</div>
<%= end %>
確認
ブラウザで記事詳細ページを開いてみると
このように、コメント投稿フォームが追加されていることが確認できると思います。
試しにコメントを投稿してみると…
このように、無事コメントの投稿ができることが確認されました。
やったね。
終わりに
以上で、当初想定していた機能は一通り実装することができました。
Phoenixを触るのは全くの初めての状態から開始でしたが、ところどころ躓いたりもしましたが、概ね問題なくここまで終わらせることができました。
Railsやそれに影響を受けたフレームワークを扱ったことがある人なら、さほど苦労なく導入最初の段階は超えられると思われます。(その先はまだ不明)
今後は、このプロジェクトをベースに様々な機能の実験をしていこうと思います。