はじめに
この記事は 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に追加します
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
スキーマファイルにリレーションの設定を行います
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の設定をします
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
検索ページの雛形作成
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
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
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を作成したので、そちらのファイルを使いまわします
関連先の本棚を選択するセレクトボックスを追加
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
モーダル表示制御を実装
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
ルーティングを追加
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をクリックすると以下のフォームが表示されます
本棚に登録した本を仮表示
登録後は追加でいくつか本を登録するのを考慮して、そのままページ推移は行いません
また、登録した本は各本棚のページから閲覧できるようにします
本棚を取得する時に一緒に本も取得するようにしています
- def get_shelf!(id), do: Repo.get!(Shelf,id)
+ def get_shelf!(id), do: Shelf |> preload(:books) |> Repo.get!(id)
streamsにshelf.booksを追加します
※以下は確認用で最終的には消すので、飛ばしても構いません
@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からコピーしてアクション周りを削ります
<.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" />
...
無事本棚に、本が登録されているのを確認できました
最後に
本記事ではリレーションの構築と雛形作成から、phx.gen.liveで作成したコンポーネントをコピー&修正して、本の登録フォームと一覧を作成しました
次回はGoogleBookAPIのデータを使って本を追加します