12
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ElixirAdvent Calendar 2023

Day 10

ElixirDesktopリレーションの構築とコンポーネント改造でフォーム作成

Last updated at Posted at 2023-12-10

はじめに

この記事は Elixirアドベントカレンダーのシリーズ4の10日目の記事です

GoogleBookAPIを使用して本のデータを取得するのですが、その下準備として本棚に本を登録するための関数とリレーションを作成します

Books contextの作成

本を保存するコンテキストと画面を作成します
LiveViewのファイルをそのままは使わないのですが、ベースとして使うので一緒に作成します

mix phx.gen.live Books Book books title:string author:string thumb:string isbn:string published_date:date shelf_id:references:shelves

アプリ用マイグレーションを作成します
priv/repo/migrations/xxxx_create_books.exs
lib/bookshelf/migrations にコピーして exs -> exに変更します

Repoのmigrationに追加します

lib/bookshelf/repo.ex
  def migration() do
    Ecto.Migrator.up(Bookshelf.Repo, 20_231_207_144_335, Bookshelf.Repo.Migrations.CreateShelves)
+   Ecto.Migrator.up(Bookshelf.Repo, 20_231_210_160_844, Bookshelf.Repo.Migrations.CreateBooks)
  end

スキーマファイルにリレーションの設定を行います

lib/bookshelf/books/book.ex
defmodule Bookshelf.Books.Book do
  use Ecto.Schema
  import Ecto.Changeset

  schema "books" do
    field :author, :string
    field :isbn, :string
    field :published_date, :date
    field :thumb, :string
    field :title, :string
-   field :shelf_id, :id
+   belongs_to :shelf, Bookshelf.Shelves.Shelf

    timestamps(type: :utc_datetime)
  end

  @doc false
  def changeset(book, attrs) do
    book
-   |> cast(attrs, [:title, :author, :thumb, :isbn, :published_date])
-   |> validate_required([:title, :author, :thumb, :isbn, :published_date])
+   |> cast(attrs, [:title, :author, :thumb, :isbn, :published_date, :shelf_id])
+   |> validate_required([:title, :author, :thumb, :isbn, :published_date, :shelf_id])
  end
end

関連先のshelfにhas_manyの設定をします

lib/bookshelf/shelves/shelf.ex
defmodule Bookshelf.Shelves.Shelf do
  use Ecto.Schema
  import Ecto.Changeset

  schema "shelves" do
    field :name, :string

+   has_many :books, Bookshelf.Books.Book

    timestamps(type: :utc_datetime)
  end

  @doc false
  def changeset(shelf, attrs) do
    shelf
    |> cast(attrs, [:name])
    |> validate_required([:name])
  end
end

検索ページの雛形作成

lib/bookshelf_web/live/search_live/index.ex
defmodule BookshelfWeb.SearchLive.Index do
  use BookshelfWeb, :live_view

  @impl true
  def render(assigns) do
    ~H"""
    <.gheader title="Search" />
    <.bottom_tab title="Searh" />
    """
  end

  @impl true
  def mount(_params, _session, socket) do
    {:ok, socket}
  end
end
lib/bookshelf_web/components/navigation.ex
  defp links() do
    [
      {"Shelf", "hero-book-open-solid", "/shelves"},
-     {"Search", "hero-magnifying-glass-solid", "/shelves"},
+     {"Search", "hero-magnifying-glass-solid", "/search"},
      {"Setting", "hero-cog-6-tooth-solid", "/shelves"}
    ]
  end
lib/bookshelf_web/router.ex
  scope "/", BookshelfWeb do
    pipe_through :browser

    get "/", PageController, :home

    live "/shelves", ShelfLive.Index, :index
    live "/shelves/new", ShelfLive.Index, :new
    live "/shelves/:id/edit", ShelfLive.Index, :edit
    live "/shelves/:id/delete", ShelfLive.Index, :delete

    live "/shelves/:id", ShelfLive.Show, :show
    live "/shelves/:id/show/edit", ShelfLive.Show, :edit
+   live "/search", SearchLive.Index, :index  
  end

登録Formの作成

phx.gen.liveでbooksを作成したので、そちらのファイルを使いまわします

関連先の本棚を選択するセレクトボックスを追加

lib/bookshelf_web/live/book_live/form_component.ex
defmodule BookshelfWeb.BookLive.FormComponent do
  use BookshelfWeb, :live_component

  alias Bookshelf.Books
+ alias Bookshelf.Shelves

  @impl true
  def render(assigns) do
    ~H"""
    <div>
      <.header>
        <%= @title %>
-       <:subtitle>Use this form to manage book records in your database.</:subtitle>
      </.header>

      <.simple_form
        for={@form}
        id="book-form"
        phx-target={@myself}
        phx-change="validate"
        phx-submit="save"
      >
        <.input field={@form[:title]} type="text" label="Title" />
        <.input field={@form[:author]} type="text" label="Author" />
        <.input field={@form[:thumb]} type="text" label="Thumb" />
        <.input field={@form[:isbn]} type="text" label="Isbn" />
        <.input field={@form[:published_date]} type="date"label="Published date" />
+       <.input field={@form[:shelf_id]} type="select" label="Shelf" options={@shelf_options} />        
        <:actions>
          <.button phx-disable-with="Saving...">Save Book</.button>
        </:actions>
      </.simple_form>
    </div>
    """
  end

  @impl true
  def update(%{book: book} = assigns, socket) do
    changeset = Books.change_book(book)
+   options = Shelves.list_shelves() |> Enum.map(&{&1.name, &1.id})

    {:ok,
     socket
     |> assign(assigns)
+    |> assign(:shelf_options, options)
     |> assign_form(changeset)}
  end
  ...
end

モーダル表示制御を実装

lib/bookshelf_web/live/search_live/index.ex
defmodule BookshelfWeb.SearchLive.Index do
  use BookshelfWeb, :live_view

  alias Bookshelf.Books.Book

  @impl true
  def render(assigns) do
    ~H"""
    <.gheader title="Search" />
+    <.link patch={~p"/search/new"}>
+      <.button>New Book</.button>
+    </.link>
    <.bottom_tab title="Search" />

+   <.modal :if={@live_action in [:new]} id="book-modal" show on_cancel={JS.patch(~p"/search")}>
+     <.live_component
+       module={BookshelfWeb.BookLive.FormComponent}
+       id={:new}
+       title={@page_title}
+       action={@live_action}
+       book={@book}
+       patch={~p"/search"}
+     />
    </.modal>
    """
  end

  @impl true
  def mount(_params, _session, socket) do
    {:ok, socket}
  end

+ @impl true
+ def handle_params(params, _url, socket) do
+   {:noreply, apply_action(socket, socket.assigns.live_action, params)}
+ end

+ defp apply_action(socket, :new, _params) do
+   socket
+   |> assign(:page_title, "New Book")
+   |> assign(:book, %Book{})
+ end

+ defp apply_action(socket, :index, _params) do
+   socket
+   |> assign(:page_title, "Listing Search")
+   |> assign(:book, nil)
+ end
end

ルーティングを追加

lib/bookshelf_web/router.ex
 scope "/", BookshelfWeb do
    pipe_through :browser

    get "/", PageController, :home

    live "/shelves", ShelfLive.Index, :index
    live "/shelves/new", ShelfLive.Index, :new
    live "/shelves/:id/edit", ShelfLive.Index, :edit
    live "/shelves/:id/delete", ShelfLive.Index, :delete

    live "/shelves/:id", ShelfLive.Show, :show
    live "/shelves/:id/show/edit", ShelfLive.Show, :edit
    live "/search", SearchLive.Index, :index
+   live "/search/new", SearchLive.Index, :new
  end

new bookをクリックすると以下のフォームが表示されます

スクリーンショット 2023-12-11 1.58.12.png

本棚に登録した本を仮表示

登録後は追加でいくつか本を登録するのを考慮して、そのままページ推移は行いません
また、登録した本は各本棚のページから閲覧できるようにします

本棚を取得する時に一緒に本も取得するようにしています

lib/bookshelf/shelves.ex
- def get_shelf!(id), do: Repo.get!(Shelf,id)
+ def get_shelf!(id), do: Shelf |> preload(:books) |> Repo.get!(id)

streamsにshelf.booksを追加します

※以下は確認用で最終的には消すので、飛ばしても構いません

lib/bookshelf_web/live/shelf_live/show.ex
  @impl true
  def handle_params(%{"id" => id}, _, socket) do
+    shelf = Shelves.get_shelf!(id)

    {:noreply,
     socket
     |> assign(:page_title, page_title(socket.assigns.live_action))
+     |> stream(:books, shelf.books)
-    |> assign(:shelf, Shelves.get_shelf!(id))}
+    |> assign(:shelf, shelf)}
  end

tableをphx.gen.liveで作成したbook_live/indexからコピーしてアクション周りを削ります

lib/bookshelf_web/live/shelf_live/show.html.heex
 <.gheader title={@shelf.name}>
  <:back>
    <.link navigate={~p"/shelves"}>
      <.icon name="hero-chevron-left-solid" class="h-6 w-6"/>
    </.link>
  </:back>

  <:actions>
    <.link patch={~p"/shelves/#{@shelf}/show/edit"} phx-click={JS.push_focus()}>
      <.icon name="hero-pencil-square-solid" class="h-6 w-6 mr-4" />
    </.link>
  </:actions>
 </.gheader>
<.list>
  <:item title="Name"><%= @shelf.name %></:item>
</.list>

+ <.table
+  id="books"
+  rows={@streams.books}
+ >
+  <:col :let={{_id, book}} label="Title"><%= book.title %></:col>
+  <:col :let={{_id, book}} label="Author"><%= book.author %></:col>
+  <:col :let={{_id, book}} label="Thumb"><%= book.thumb %></:col>
+  <:col :let={{_id, book}} label="Isbn"><%= book.isbn %></:col>
+  <:col :let={{_id, book}} label="Published date"><%= book.published_date %></:col>
+ </.table>

<.bottom_tab title="Shelf" />
...

無事本棚に、本が登録されているのを確認できました

スクリーンショット 2023-12-11 2.08.38.png

最後に

本記事ではリレーションの構築と雛形作成から、phx.gen.liveで作成したコンポーネントをコピー&修正して、本の登録フォームと一覧を作成しました

次回はGoogleBookAPIのデータを使って本を追加します

12
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?