Phoenix とExpoで作るスマホアプリ ①Phoenix セットアップ編 + phx_gen_auth
Phoenix とExpoで作るスマホアプリ ②JWT認証+CRUD編
Phoenix とExpoで作るスマホアプリ ③ファイルアップロード編
[Phoenix とExpoで作るスマホアプリ ④多対多のリレーション編]
(https://qiita.com/the_haigo/items/f3ecb242a8fbb9936c4b/) <- 本記事
前回はファイルアップロード機能を実装しました
今回はタグのような多対多のリレーションを作成してAPIで操作をしていきたいと思います
tagモデルと中間テーブルを作成していきましょう
Tagモデルの作成
mix phx.gen.json Tags Tag tags name:string user_id:references:users
mix ecto.gen.migration create_tags_posts
defmodule Sns.Repo.Migrations.CreateTagsPosts do
use Ecto.Migration
def change do
create table(:tags_posts) do
add :tag_id, references(:tags)
add :post_id, references(:posts)
end
create unique_index(:tags_posts, [:tag_id, :post_id])
end
end
json.genで作成したモデルにリレーションを設定していきます
join_throughには先程作成した中間テーブルを
on_replaceは postにelixir phoenixのタグがついている時に、post.tags = [nerves]で置き換えた時にelixir phoenix nervesと増やすのか、nervesだけにするのかの挙動を決めます
今回はon_replaceなので後者になります
on_deleteは元のレコードを消した時に一緒に消すのかの挙動を設定します
詳しい内容は公式をどうぞ
defmodule Sns.Tags.Tag do
use Ecto.Schema
import Ecto.Changeset
schema "tags" do
field :name, :string
belongs_to :user, Sns.Users.User
many_to_many :posts,
Sns.Posts.Post,
join_through: "tags_posts",
on_replace: :delete,
on_delete: :delete_all
timestamps()
end
@doc false
def changeset(tag, attrs) do
tag
|> cast(attrs, [:name])
|> validate_required([:name])
end
end
defmodule Sns.Posts.Post do
....
schema "posts" do
field :body, :string
belongs_to :user, Sns.Users.User
has_many :images, Sns.Images.Image
many_to_many :tags,
Sns.Tags.Tag,
join_through: "tags_posts",
on_replace: :delete,
on_delete: :delete_all
timestamps()
end
....
end
mix ecto.migrate
defmodule SnsWeb.Api.V1.TagController do # <- Api.V1を追加
...
end
defmodule SnsWeb.Api.V1.TagView do # <- Api.V1を追加
use SnsWeb, :view
alias SnsWeb.Api.V1.TagView # <- Api.V1を追加
...
end
defmodule SnsWeb.Router do
...
scope "/api/v1", SnsWeb do
pipe_through [:api, :jwt_authenticated]
get "/mypage", Api.V1.UserController, :show
resources "/posts", Api.V1.PostController, except: [:new, :edit]
resources "/images", Api.V1.ImageController, except: [:new, :edit]
resources "/tags", Api.V1.TagController, except: [:new, :edit] # <- ここ追加
end
...
end
tag作成時にuser_idを入れるのも忘れずに
defmodule SnsWeb.Api.V1.TagController do
...
def create(conn, %{"tag" => tag_params}) do
with {:ok, %Tag{} = tag} <- Tags.create_tag(
Map.put(tag_params, "user_id", conn.user_id) # <- ここ変更
) do
conn
|> put_status(:created)
|> put_resp_header("location", Routes.tag_path(conn, :show, tag))
|> render("show.json", tag: tag)
end
end
...
end
タグ機能の実装
これで準備が整いましたのでpostにタグを付ける機能を実装していきましょう
defmodule SnsWeb.Api.V1.PostController do
...
def add_tag(conn, %{"id" => id, "tags" => params}) do
post = Posts.get_post_with_tags!(id)
with {:ok, %Post{} = post} <- Posts.add_tag(post, params) do
render(conn, "tags.json", post: post)
end
end
...
end
リレーション先を変更する際はpreloadする必要があるので
記事をタグ付きで取得する get_post_with_tag!と
タグを登録するadd_tagを実装します
defmodule Sns.Posts do
...
def get_post_with_tags!(id) do
Post
|> preload(:tags)
|> Repo.get!(id)
end
def add_tag(%Post{} = post, ids) do
post
|> Ecto.Changeset.change
|> Ecto.Changeset.put_assoc(:tags, Sns.Tags.get_tags!(ids))
|> Repo.update()
end
...
end
put_assocにに渡すidのリストからタグを取得する Tag.get_tags!を実装します
defmodule Sns.Tags do
...
def get_tags!(ids) do
Tag
|> where([t], t.id in ^ids)
|> Repo.all
end
...
end
結果を登録したタグの一覧で欲しいのでviewを追加します
defmodule SnsWeb.Api.V1.PostView do
use SnsWeb, :view
alias SnsWeb.Api.V1.PostView
alias SnsWeb.Api.V1.ImageView
alias SnsWeb.Api.V1.TagView # <- tagのviewをTagViewで使うために追加
...
def render("tags.json", %{post: post}) do
%{data: render_many(post.tags, TagView, "tag.json")}
end
...
end
end
次にroutingを追加
defmodule SnsWeb.Router do
...
scope "/api/v1", SnsWeb do
pipe_through [:api, :jwt_authenticated]
get "/mypage", Api.V1.UserController, :show
post "/posts/add_tag", Api.V1.PostController, :add_tag # <- ここ追加
resources "/posts", Api.V1.PostController, except: [:new, :edit]
resources "/images", Api.V1.ImageController, except: [:new, :edit]
resources "/tags", Api.V1.TagController, except: [:new, :edit]
end
...
end
以上で実装が完了したので動作確認を行っていきましょう
動作確認
postmanで配列データを送信する際はtags[]と値のペアを複数セットする必要があるので注意してください
id 2のタグだけが返ってきました
これで多対多のリレーションを作成しレコードを追加することができました
今回は以上になりますありがとうございました
次回はついにフロント部分に入っていきます
今回の差分