はじめに
ひとりLiveView Advent Calendar の4日目の記事です
この記事はElixir Conf US 2021の発表したシステムの構築と関連技術の解説を目的とした記事です
今回は以下の4つを実装します
- phx.gen.jsonでAPI作成
- phx.gen.schemaでモデルのみ作成
- 手動でAPI作成
- routerにエンドポイント追加
phx.gen.json
以下のコマンドでAPIとモデル、クエリーを作成しますが、一日目でphx.gen.liveで作ったので、
--no-schema --no-contextでAPIのみ作成しています
mix phx.gen.json Loggers Map maps name:string description:string --no-schema --no-context
* creating lib/live_logger_web/controllers/map_controller.ex
* creating lib/live_logger_web/views/map_view.ex
* creating test/live_logger_web/controllers/map_controller_test.exs
* creating lib/live_logger_web/views/changeset_view.ex
* creating lib/live_logger_web/controllers/fallback_controller.ex
Add the resource to your :api scope in lib/live_logger_web/router.ex:
resources "/maps", MapController, except: [:new, :edit]
作成されたファイルを見てみましょう
- lib/live_logger_web/controllers/map_controller.ex
CRUD APIが書かれています - lib/live_logger_web/views/map_view.ex
APIのレスポンスを記述します controllerとviewファイルはセットなので忘れないようにしましょう - lib/live_logger_web/controllers/fallback_controller.ex
action_fallbackで指定されていて各APIのエラー時にfallback_controllerのエラーにハンドリングされます - lib/live_logger_web/views/changeset_view.ex
422 error時のレスポンスが記載されています - creating test/live_logger_web/controllers/map_controller_test.exs
テストも自動生成やったぜ
phx.gen.schema
モデル単体だけを作成したい場合は phx.gen.schemaを実行します
クエリーも作成する場合は phx.gen.contextになります
mix phx.gen.schema Loggers.Point points lat:float lng:float device_id:string map_id:references:maps
mix ecto.migrate
モデルファイルとマイグレーションファイルのみ作成されます
* creating lib/live_logger/loggers/point.ex
* creating priv/repo/migrations/20211204133145_create_points.exs
Remember to update your repository by running migrations:
$ mix ecto.migrate
migrateも忘れずに
mix ecto.migrate
PointのクエリーをLoggersに追加します
aliasで構造体をPointで呼び出せるようにして
list,createを実装します
query関数内で外部の引数を使用する場合はプリミティブな型でピン演算子を付ける必要があります
createはcreate_mapを参考に書きましょう
defmodule LiveLogger.Loggers do
...
alias LiveLogger.Loggers.Point
...
def list_points(map_id) do
Point
|> where([p], p.map_id == ^map_id)
|> Repo.all
end
def create_point(attrs \\ %{}) do
%Point{}
|> Point.changeset(attrs)
|> Repo.insert
end
end
リレーションも組んでいきます
defmodule LiveLogger.Loggers.Map do
use Ecto.Schema
import Ecto.Changeset
schema "maps" do
field :description, :string
field :name, :string
belongs_to :user, LiveLogger.Accounts.User
has_many :points, LiveLogger.Loggers.Point
timestamps()
end
...
end
defmodule LiveLogger.Loggers.Point do
use Ecto.Schema
import Ecto.Changeset
schema "points" do
field :device_id, :string
field :lat, :float
field :lng, :float
belongs_to :map, LiveLogger.Loggers.Map #field map_idを belongs_toに変更
timestamps()
end
@doc false
def changeset(point, attrs) do
point
|> cast(attrs, [:lat, :lng, :device_id, :map_id]) # map_id追加
|> validate_required([:lat, :lng, :device_id, :map_id]) # map_id追加
end
end
Map詳細でpointも取得するようにしましょう
preloadで関連先のデータを読み込みます
defmodule LiveLogger.Loggers do
...
def get_map_with_points!(id) do
Map
|> preload(:points)
|> Repo.get!(id)
end
...
end
手動でAPI作成
generatorなしで作りますが特に難しくありません
contextとモデルのモジュールをaliasで読み込む
action_fallbackを指定
apiを追加
レスポンスはsend_respのみでJSONは何も返さないためViewファイルはありません
defmodule LiveLoggerWeb.PointController do
use LiveLoggerWeb, :controller
alias LiveLogger.Loggers
alias LiveLogger.Loggers.Point
action_fallback LiveLoggerWeb.FallbackController
def create(conn, point_params) do
with {:ok, %Point{}} <- Loggers.create_point(point_params) do
send_resp(conn, 200, "ok")
end
end
end
endpoint追加
routerのコメントアウトされている api scopeをコメントインして
コンソールに出てきてたroutingのコードを追加
pointsはcreateだけなので onlyで指定しています
defmodule LiveLoggerWeb.Router do
use LiveLoggerWeb, :router
...
# Other scopes may use custom stacks.
scope "/api", LiveLoggerWeb do
pipe_through :api
resources "/maps", MapController, except: [:new, :edit]
resources "/points", PointController, only: [:create]
end
end
動作確認
動作確認はPostmanを使用します
get api/maps/1
post api/maps
createの引数が %{map: map_params} = params なので map[カラム名]でjson形式になるようにすること
delete api/maps/2
JSONのレスポンスはなく 204 No Contentが帰ってきています
create api/points
mapと違って point_paramsとしているのでフラットなformdataで大丈夫です
最後に
phx.gen.jsonによってAPIの実装も簡単にできました
json responseからdataが邪魔だなーと思ったら以下のようにすれば消すことができます
defmodule LiveLoggerWeb.MapView do
use LiveLoggerWeb, :view
alias LiveLoggerWeb.MapView
def render("index.json", %{maps: maps}) do
render_many(maps, MapView, "map.json") # dataとMapの囲いを消す
end
...
end
本記事は以上になります
次はAPIでの認証を実装します
Code