スポンサーリンク

2015年8月2日

[Elixir+Phoenix]Operation of Micropost

Goal

マイクロポストの投稿と削除を実装する。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.4
Phoenix Framework: v0.13.1
PostgreSQL: postgres (PostgreSQL) 9.4.4

Wait a minute

いや~、前回の記事では修正量が多くてすませんでした。
お付き合い頂いた方には感謝です。
マイクロポストを画面から投稿/削除できるようにします。
やったね!iexから地味な作業をしなくても、操作できるようになるよ!!
後、フィードは今回実装しません。
詳しくは独り言を読んで下さい。

Index

Operation of Micropost
|> Creating a Micropost controller
|> Sign-in required
|> Micropost Posts
|> Delete of Micropost

Creating a Micropost controller

マイクロポストを投稿/削除を実装するため、
Create、Deleteアクションが必要です。
そのためのコントローラを作成します。
ファイル: web/router.ex
ルーティングを追加。
resources "/post", MicropostController, only: [:create, :delete]
Description:
only:[action]で特定のアクションのみ選択している。
今回は、createとdeleteアクションを選択。
気になる人は、以下のコマンドで確認してみる。
mix phoenix.routes
ファイル: web/controllers/micropost_controller.ex
以下のように編集。
defmodule SampleApp.MicropostController do
  use SampleApp.Web, :controller

  plug SampleApp.Plugs.CheckAuthentication
  plug :action

  def create(conn, _params) do
  end

  def delete(conn, _params) do
  end
end

Sign-in required

サインイン状態でないと操作をできないようにします。
UserControllerでやっているように、MicropostControllerにも
signed_in_user?/2 を実装すればいいのでしょうか?
それは機能の重複ですね。ナンセンスです。
なので、signed_in_user?/2 をCheckAuthenticationと同じようにモジュールプラグに変更します。
ファイル: lib/plugs/signed_in_user.ex
以下のように実装。
defmodule SampleApp.Plugs.SignedInUser do
  import Plug.Conn
  import Phoenix.Controller, only: [put_flash: 3, redirect: 2]
  import SampleApp.Router.Helpers, only: [session_path: 2]

  def init(options) do
    options
  end

  def call(conn, _) do
    if conn.assigns[:current_user] do
      conn
    else
      conn
      |> put_flash(:info, "Please sign-in.")
      |> redirect(to: session_path(conn, :new))
      |> halt
    end
  end
end
Description:
移しただけですね。しかし、ちょっとだけ説明を書きます。
importで特定の使いたい関数だけ取り込んでいます。
ファイル: web/controllers/user_controller.ex
以下の二つを削除。
plug :signed_in_user? when action in [:index, :show, :edit, :update, :delete]
defp signed_in_user?(conn, _) do
...
end
以下を追加。
機能プラグの部分がモジュールプラグを指定するようになっただけですね。
plug SampleApp.Plugs.SignedInUser when action in [:index, :show, :edit, :update, :delete]
ファイル: web/controllers/micropost_controller.ex
以下を追加。
plug SampleApp.Plugs.SignedInUser
これで準備よし。

Micropost Posts

マイクロポストの投稿を実装します。
Createアクションの実装です。
ファイル: web/models/micropost.ex
関数の追加。
def new_changeset(model, params \\ :empty) do
  model |> cast(params, @required_fields, @optional_fields)
end
ファイル: web/controllers/user_controller.ex
以下のように編集。
def show(conn, %{"id" => id, "select_page" => select_page}) do
  user = Repo.get(SampleApp.User, id)
  page = SampleApp.Micropost.paginate(user.id, select_page)
  changeset = SampleApp.Micropost.new_changeset(
      %SampleApp.Micropost{}, %{content: "", user_id: user.id})

  if page do
    render(conn, "show.html",
           user: user,
           posts: page.entries,
           current_page: page.page_number,
           total_pages: page.total_pages,
           page_list: Range.new(1, page.total_pages),
           changeset: changeset)
  else
    conn
    |> put_flash(:error, "Invalid page number!!")
    |> render("show.html", user: user, posts: [])
  end
end
ファイル: web/templates/user/show.html.eex
以下のソースコードを最後に追加。
<%= alias SampleApp.LayoutView %>
<h2>User profile</h2>

<div style="float: left; margin-top: 20px; margin-right: 20px;">
  <img src="<%= get_gravatar_url(@user) %>" class="gravatar">
  <h2><%= @user.name %></h2>
</div>

<div style="clear: right; margin-left: 150px;">
  <%= form_for @changeset, micropost_path(@conn, :create), fn f -> %>
    <%= if f.errors != [] do %>
      <div class="alert alert-danger">
        <p>Oops, something went wrong! Please check the errors below:</p>
        <ul>
          <%= for {attr, message} <- f.errors do %>
            <li><%= humanize(attr) %> <%= message %></li>
          <% end %>
        </ul>
      </div>
    <% end %>

    <%= hidden_input f, :user_id %>

    <div class="form-group">
      <label>Content</label>
      <%= textarea f, :content, class: "form-control" %>
    </div>

    <div class="form-group">
      <%= submit "Post", class: "btn btn-primary" %>
    </div>
  <% end %>
</div>

<div style="clear: left;">
  <%= link "Edit", to: user_path(@conn, :edit, @user), class: "btn btn-default btn-xs" %>
  <%= link "Delete", to: user_path(@conn, :delete, @user), method: :delete, class: "btn btn-danger btn-xs" %>
</div>

<div style="float: right; margin-right: 50px;">
  <h2>Microposts</h2>

  <%= if !is_empty_list?(@posts) do %>
    <h2>
      <%= for post <- @posts do %>
        <li style="padding: 10px 0; border-top: 1px solid #e8e8e8;"><%= post.content %> <%= post.inserted_at %></li>
      <% end %>
    </h2>

    <%= render "pagination.html",
               action: user_path(@conn, :show, LayoutView.current_user(@conn).id),
               current_page: @current_page,
               page_list: @page_list,
               total_pages: @total_pages %>
  <% end %>

</div>
ファイル: web/controllers/micropost_controller.ex
以下のように編集。
plug :scrub_params, "micropost" when action in [:create]
def create(conn, %{"micropost" => micropost_params}) do
  changeset = SampleApp.Micropost.changeset(%SampleApp.Micropost{}, micropost_params)

  if changeset.valid? do
    Repo.insert(changeset)
    conn = put_flash(conn, :info, "Post successfully!!")
  else
    conn = put_flash(conn, :error, "Post failed!!")
  end

  action = "#{user_path(conn, :show, conn.assigns[:current_user].id)}?select_page=1"
  redirect(conn, to: action)
end
これで画面から投稿できるようになった。

Delete of Micropost

マイクロポストの削除機能を実装しましょう。
Deleteアクションの実装です。
ファイル: web/controllers/micropost_controller.ex
以下のように編集。
def delete(conn, %{"id" => id}) do
  micropost = Repo.get(SampleApp.Micropost, id)
  Repo.delete(micropost)

  action = "#{user_path(conn, :show, conn.assigns[:current_user].id)}?select_page=1"

  conn
  |> put_flash(:info, "Micropost deleted successfully.")
  |> redirect(to: action)
end
ファイル: web/templates/user/show.html.eex
削除のリンクを追加。
<%= for post <- @posts do %>
  <li style="padding: 10px 0; border-top: 1px solid #e8e8e8;">
    <%= post.content %>
    <%= post.inserted_at %>
    <%= link "Delete", to: micropost_path(@conn, :delete, post), method: :delete, class: "btn btn-danger btn-xs" %>
  </li>
<% end %>

Speaking to oneself

フィードですが、ここでは実装しません。
Tutorialと別枠でお知らせとRSSといった形でフィードを実装しようと思います。
(それだけで、割と文字数食うのため)
Tutorialが早く終わることを祈って下さい。
そういえば、SQLインジェクション対策してませんね。
Ecto.Queryを使って自分でQueryを記述する時は、preparedがありましたね。
Ectoの中で上手く変換している気がします。
確認のため、SQL(select、insert)を入力してみましたが、
ただの文字列として退避されるみたいですね。
(予想通り~)
これで第十章は終わり。後はまとめ記事ですね。
has_manyとbelongs_toの機能を全く使ってないことに気付く。
あれを上手く使うのはどうすればいいんだろうか・・・
どうでもいいことなんですけど、
何故かdeleteアクションのソースコードだけハイライトが付かない・・・

Bibliography

人気の投稿