fukuoka.ex Elixir/Phoenix Advent Calendar 2021、昨日@torifukukaiouさんで「【Elixir】次の関数の第2引数なんだよな〜 2021年12月 (2021/12/03)」でした
##①プロジェクトを作成【作業】
mix phx.new コマンドを使ってPhoenixのプロジェクトを作成します ※なおWindows(WSL2未使用)をお使いの方は、先にコチラを実行してから下記を実施してください
mix phx.new sns --database sqlite3
mix phx.new プロジェクト名 --database sqlite3
--database オプションでDBのプラットフォームを指定します
今回はsqlite3を使います
依存関係を取得してインストールするかを聞かれますのでYで続行
Fetch and install dependencies? [Yn] Y
プロジェクトの作成が完了するこのような画面が表示します
We are almost there! The following steps are missing:
$ cd sns
Then configure your database in config/dev.exs and run:
$ mix ecto.create
Start your Phoenix app with:
$ mix phx.server
You can also run your app inside IEx (Interactive Elixir) as:
$ iex -S mix phx.server
Windows(WSL2未使用)ではBuild Tools for Visual Studioが必要
【2021/12/6追記】Build Tools for Visual Studio 2022にて下記を検証しました。
Windowsネイティブで動かしている方は、sqlite3のビルドに「Build Tools for Visual Studio」のインストールが必要です。
まず、下記コラムでインストールを行ってください。なお、「clang」のダウンロード/インストールは不要です。
https://qiita.com/piacerex/items/840a2679f8c4382c453a
次に、スタートメニューから「x86 Native Tools Command Prompt for VS 2022」を起動してから、上記手順を実施してください。
##②データベースの作成【作業】
まず、cdコマンドでsnsディレクトリに移動します
$ cd sns
次に mix ecto.crateでデータベースを作成します
$ mix ecto.create
sns_dev.dbが作成されます
このファイルはsqlite3で使われるデータベースのファイルです
確認方法
Mac / Linux
$ ls
Windows
dir
#③LiveViewのページを作成【作業】
mix phx.gen.liveコマンドを使う
mix phx.gen.live Messages Message messages contents:text
mix phx.gen.live コンテキストモジュール名 スキーマモジュール名 スキーマテーブル名 カラム名:型
LiveViewページ作成が完了するこのような画面が表示されます
Add the live routes to your browser scope in lib/sns_web/router.ex:
live "/messages", MessageLive.Index, :index
live "/messages/new", MessageLive.Index, :new
live "/messages/:id/edit", MessageLive.Index, :edit
live "/messages/:id", MessageLive.Show, :show
live "/messages/:id/show/edit", MessageLive.Show, :edit
Remember to update your repository by running migrations:
$ mix ecto.migrate
ここで2点作業が必要です
1、Router.exを書き換える
2、データベースのマイグレート
##④ルーターを書き換える【作業】
ルーターとは
ブラウザーからのリクエストを元にどのモジュールの関数を実行するかを記述する場所です
lib/sns_web/router.exを開きます
変更前
#中略
scope "/", SnsWeb do
pipe_through :browser
#ここに記述します
get "/", PageController, :index
end
#中略
記述例
#中略
scope "/", SnsWeb do
pipe_through :browser
live "/messages", MessageLive.Index, :index
live "/messages/new", MessageLive.Index, :new
live "/messages/:id/edit", MessageLive.Index, :edit
live "/messages/:id", MessageLive.Show, :show
live "/messages/:id/show/edit", MessageLive.Show, :edit
get "/", PageController, :index
end
#中略
##⑤データベースのマイグレート【作業】
mix ecto.migrateコマンドを使う
$ mix ecto.migrate
実行結果
Generated sns app
19:12:40.993 [info] == Running 20211120095944 Sns.Repo.Migrations.CreateMessages.change/0 forward
19:12:41.000 [info] create table messages
19:12:41.002 [info] == Migrated 20211120095944 in 0.0s
##⑥Phoenixを起動【作業】
mix phx.serverコマンドを使う
$ mix phx.server
起動
[info] Running SnsWeb.Endpoint with cowboy 2.9.0 at 127.0.0.1:4000 (http)
[debug] Downloading esbuild from https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.12.18.tgz
[info] Access SnsWeb.Endpoint at http://localhost:4000
[watch] build finished, watching for changes...
[info] GET /messages
[debug] Processing with Phoenix.LiveView.Plug.index/2
Parameters: %{}
Pipelines: [:browser]
[debug] QUERY OK source="messages" db=0.0ms queue=0.1ms idle=1092.1ms
SELECT m0."id", m0."contents", m0."inserted_at", m0."updated_at" FROM "messages" AS m0 []
[info] Sent 200 in 103ms
[info] CONNECTED TO Phoenix.LiveView.Socket in 88µs
Transport: :websocket
Serializer: Phoenix.Socket.V2.JSONSerializer
Parameters: %{"_csrf_token" => "Hxs3Yx8yByFpci8uHx8UFxg_BhMxMwogeLR5odLfY4nApW-HvMJWSpxn", "_mounts" => "0", "_track_static" => %{"0" => "http://localhost:4000/assets/app.css", "1" => "http://localhost:4000/assets/app.js"}, "vsn" => "2.0.0"}
[debug] QUERY OK source="messages" db=0.1ms idle=1406.9ms
SELECT m0."id", m0."contents", m0."inserted_at", m0."updated_at" FROM "messages" AS m0 []
ブラウザで http://localhost:4000/messages
にアクセス
##⑦一覧画面の中身を見てみよう【説明】
どのようにしてこのページが表示している?
#中略
scope "/", SnsWeb do
pipe_through :browser
live "/messages", MessageLive.Index, :index
#中略
ルーターについて
・live LiveViewであることを示してる
・"/messages" URL(エンドポイント)が messagesを示している
・MessageLive.Index SnsWeb.MessageLive.Indexモジュールを呼び出すことを示してる
・ :index indexアクションを示してる
SnsWeb.MessageLive.Indexモジュールを見てみよう
defmodule SnsWeb.MessageLive.Index do
use SnsWeb, :live_view
## 中略
@impl true
def mount(_params, _session, socket) do
{:ok, assign(socket, :messages, list_messages())}
end
#中略
defp list_messages do
Messages.list_messages()
end
・LiveViewは初めにmountが呼ばれます
・list_messagesでデータベースから情報を取得します
・assignで表示する内容を渡しています
handle_params→apply_actionの:indexを呼びます
#中略
def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end
defp apply_action(socket, :index, _params) do
socket
|> assign(:page_title, "Listing Messages")
|> assign(:message, nil)
end
#中略
データーベースはどうやって呼ばれてる?
Repo.all(Message)で結果を取得してます
#中略
defmodule Sns.Messages do
@moduledoc """
The Messages context.
"""
import Ecto.Query, warn: false
alias Sns.Repo
alias Sns.Messages.Message
def list_messages do
Repo.all(Message)
end
#中略
「elixir ecto」で検索するとこの仕組みを調べるヒントになります
Sns.Messages.Messageってどうなってる?
テーブルはmessages
フィールドはcontentsで文字型であることがわかります
defmodule Sns.Messages.Message do
use Ecto.Schema
import Ecto.Changeset
schema "messages" do
field :contents, :string
timestamps()
end
#中略
テンプレートを見てみよう
#中略
<%= for message <- @messages do %>
<tr id={"message-#{message.id}"}>
<td><%= message.contents %></td>
<td>
<span><%= live_redirect "Show", to: Routes.message_show_path(@socket, :show, message) %></span>
<span><%= live_patch "Edit", to: Routes.message_index_path(@socket, :edit, message) %></span>
<span><%= link "Delete", to: "#", phx_click: "delete", phx_value_id: message.id, data: [confirm: "Are you sure?"] %></span>
</td>
</tr>
<% end %>
#中略
・list_messages() の結果が@messagesに格納されてます
・forで@messagesの件数分<% end %>までの内容が出力されます
・messageは1件のデータです
・<%= message.contents %> でcontentsフィールドの内容を表示します
router.exを見てみよう
#中略
scope "/", SnsWeb do
#中略
pipe_through :browser
#中略
live "/messages/new", MessageLive.Index, :new
#中略
・live LiveViewであることを示してる
・"/messages/new" URL(エンドポイント)が messagesを示している
・MessageLive.Index SnsWeb.MessageLive.Indexモジュールを呼び出すことを示してる
・ :new newアクションを示してる
SnsWeb.MessageLive.Indexモジュールを見てみよう
defp apply_action(socket, :new, _params)が呼ばれます
#中略
defp apply_action(socket, :new, _params) do
socket
|> assign(:page_title, "New Message")
|> assign(:message, %Message{})
end
#中略
テンプレートを見てみよう
#中略
<h1>Listing Messages</h1>
<%= if @live_action in [:new, :edit] do %>
<%= live_modal SnsWeb.MessageLive.FormComponent,
id: @message.id || :new,
title: @page_title,
action: @live_action,
message: @message,
return_to: Routes.message_index_path(@socket, :index) %>
<% end %>
#中略
live_actionは:newに為live_modal SnsWeb.MessageLive.FormComponentが呼ばれます
FormComponentのテンプレート
#中略
<div>
<h2><%= @title %></h2>
<.form
let={f}
for={@changeset}
id="message-form"
phx-target={@myself}
phx-change="validate"
phx-submit="save">
<%= label f, :contents %>
<%= textarea f, :contents %>
<%= error_tag f, :contents %>
<div>
<%= submit "Save", phx_disable_with: "Saving..." %>
</div>
</.form>
</div>
#中略
form_componentを見てみよう
#中略
def handle_event("validate", %{"message" => message_params}, socket) do
changeset =
socket.assigns.message
|> Messages.change_message(message_params)
|> Map.put(:action, :validate)
{:noreply, assign(socket, :changeset, changeset)}
end
def handle_event("save", %{"message" => message_params}, socket) do
save_message(socket, socket.assigns.action, message_params)
end
defp save_message(socket, :new, message_params) do
case Messages.create_message(message_params) do
{:ok, _message} ->
{:noreply,
socket
|> put_flash(:info, "Message created successfully")
|> push_redirect(to: socket.assigns.return_to)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
#中略
・handle_event("validate", %{"message" => message_params}, socket)
入力チェック
・def handle_event("save", %{"message" => message_params}, socket) do
保存ボタンを押した時
・defp save_message(socket, :new, message_params) do
保存処理の内容
Messages.create_message(message_params) を見てみよう
create_messageでDBに1件データを追加します
#中略
def create_message(attrs \\ %{}) do
%Message{}
|> Message.changeset(attrs)
|> Repo.insert()
end
#中略
phx_click: "delete" でhandle_eventの"delete"と関連づけします
#中略
<span><%= link "Delete", to: "#", phx_click: "delete", phx_value_id: message.id, data: [confirm: "Are you sure?"] %></span>
#中略
Messages.delete_message(message)を見てみよう
#中略
def handle_event("delete", %{"id" => id}, socket) do
message = Messages.get_message!(id)
{:ok, _} = Messages.delete_message(message)
{:noreply, assign(socket, :messages, list_messages())}
end
#中略
delete_messageでデータを1件削除してます。
#中略
def get_message!(id), do: Repo.get!(Message, id)
def delete_message(%Message{} = message) do
Repo.delete(message)
end
#中略
#改造してみよう
##⑩表示内容を変更【作業】
#中略
<%= for message <- @messages do %>
<tr id={"message-#{message.id}"}>
<td>
<span><%= live_redirect "Show", to: Routes.message_show_path(@socket, :show, message) %></span>
<span><%= live_patch "Edit", to: Routes.message_index_path(@socket, :edit, message) %></span>
<span><%= link "Delete", to: "#", phx_click: "delete", phx_value_id: message.id, data: [confirm: "Are you sure?"] %></span>
<pre>
<%= message.contents %>
</pre>
</td>
</tr>
<% end %>
#中略
##⑪ヘッダーの画像を変更【作業】
ダウンロードした画像をpriv/static/images/piyo.pngへ移動
更新前
#中略
<body>
<header>
<section class="container">
<nav>
<ul>
<li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li>
<%= if function_exported?(Routes, :live_dashboard_path, 2) do %>
<li><%= link "LiveDashboard", to: Routes.live_dashboard_path(@conn, :home) %></li>
<% end %>
</ul>
</nav>
<a href="https://phoenixframework.org/" class="phx-logo">
<img src={Routes.static_path(@conn, "/images/phoenix.png")} alt="Phoenix Framework Logo"/>
</a>
</section>
</header>
<%= @inner_content %>
</body>
#中略
更新後
##中略
<body>
<header>
<section class="container">
<img src={Routes.static_path(@conn, "/images/piyo.png")} alt="PiyoPiyo Logo"/>
</section>
</header>
<%= @inner_content %>
</body>
##中略
##おわり
明日は、@kn339264さんで「Elixir女子部のオーガナイザーをやってみた話」です