Slide 1

Slide 1 text

Ectoの全体感をまとめてみる 2022.02.24 fukuoka.ex#51:Elixirお茶会〜Ectoを学ぼうの会

Slide 2

Slide 2 text

About Me @koga1020_ @koga1020 koga1020.com 👨‍💻 自己紹介 古賀 祥造(koga1020) 福岡在住のバックエンドエンジニア(最近はマネジメント寄り) fukuoka.ex 管理人 💡 最近の興味関心 Elixir・Phoenixを使ったWebアプリケーション開発 マイクロサービスの実現。実装パターンの学習 美味しいご飯・美味しいお酒 🍶 マイホームで快適に過ごすこと 🏠

Slide 3

Slide 3 text

fukuoka.exについて @piacereさんが2017年に発足 🎉 2018年3月からkoga1020がjoin。オフライン会場準備などイベント開催のお手伝い 2020年1月からすべてオンライン開催に およそ月1の頻度でイベントを開催しています

Slide 4

Slide 4 text

アジェンダ Ectoとは 公式ドキュメントに沿って分解 Ecto.Repo Ecto.Schema Ecto.Changeset Ecto.Query

Slide 5

Slide 5 text

Ectoとは This guide is an introduction to Ecto, the database wrapper and query generator for Elixir. Ecto provides a standardized API and a set of abstractions for talking to all the different kinds of databases, so that Elixir developers can query whatever database they’re using by employing similar constructs. データベースラッパー クエリジェネレーター 標準化されたAPIと、あらゆる種類のデータベースと対話するための抽象化されたセットを提供 使用しているデータベースに対して同様の構造を用いて問い合わせを行うことができる [1] 1. https://hexdocs.pm/ecto/getting-started.html#content

Slide 6

Slide 6 text

たとえば PostgreSQLを使っている場合: MySQLを使っている場合: 異なるデータベースを使っていても、DB操作は抽象化して扱うことができる defp deps do [ {:postgrex, "~> 0.15"} ] end defp deps() do [ {:myxql, "~> 0.6.0"} ] end iex> Repo.get_by(User, email: "[email protected]")

Slide 7

Slide 7 text

ざっくりいうと RailsでいうActiveRecord, LaravelのEloquentに相当する が、Elixirにオブジェクトはないため、思想や書き方は異なる Ectoの方がより明示的で、SQLに近いイメージ 「リレーション先のデータも取得して、、」(preload) などは自分で明示的に書く必要がある どこで何のcallbackが動いているかが分からなくなるよりかは明示的な方がベターと思う(個人の感想です) あわせて読みたい: What is the advantage of Ecto over Rails ORM? # other language user = User.find(1) user.confirm() # Ecto iex> user = Repo.get(User, 1) iex> UserRegister.confirm_user(user)

Slide 8

Slide 8 text

Ectoの構成要素 Ecto.Repo Ecto.Schema Ecto.Changeset Ecto.Query

Slide 9

Slide 9 text

Ecto.Repo data storeとの接続を抽象化 data storeとどうやりとりするか: Adapterを定義 Repo.insert/2 としたときの挙動はAdapterが決めている Point: data storeは必ずしもMySQLやPostgreSQLなどのデータベースである必要はない CSVを操作したりする例も ecto3_mnesia なんかもそう ※ ecto_mnesia が2系で止まって、別で ecto3_mnesia が作られている 🤔 ? 大多数の人が使うであろうPostgreSQL, MySQL, SQLServerでは公式がアダプターを提供してくれている これが ecto_sql プロジェクトに分離されている ` ` [1] ` ` [2] ` ` ` ` ` ` [3] 1. https://qiita.com/ndac_todoroki/items/48c44dd1a2e9c7a824d3 2. https://hexdocs.pm/ecto3_mnesia/Ecto.Adapters.Mnesia.html 3. https://github.com/elixir-ecto/ecto_sql

Slide 10

Slide 10 text

Ectoの構成要素 ✅ Ecto.Repo Ecto.Schema Ecto.Changeset Ecto.Query

Slide 11

Slide 11 text

Ecto.Schema 主にdata sourceから取得した値をElixirのstruct(構造体)に割り当てるために使われる 「主に」と書いたのがポイント データベースから取得したデータのマッピングはあくまでユースケースの1つ 後述の Ecto.Changeset と合わせて、データベースのレコードを割り当てるだけに留まらず活用可能 この辺は @torifukukaiou さんのLTで聞けるはず! ` `

Slide 12

Slide 12 text

Ecto.Schema schemaを定義 DBクエリ結果をマッピング defmodule User do use Ecto.Schema schema "users" do field :name, :string field :age, :integer, default: 0 end end iex> Sample.Repo.all(Sample.Accounts.User) [debug] QUERY OK source="users" db=0.1ms queue=0.1ms idle=1088.3m SELECT u0."id", u0."age", u0."name", u0."inserted_at", u0."updated_at" FR [ %Sample.Accounts.User{ __meta__: #Ecto.Schema.Metadata<:loaded, "users">, age: 20, id: 1, inserted_at: ~N[2022-02-08 14:18:05], name: "山田太郎", updated_at: ~N[2022-02-08 14:18:05] }, %Sample.Accounts.User{ __meta__: #Ecto.Schema.Metadata<:loaded, "users">, age: 30, id: 2, inserted_at: ~N[2022-02-08 14:18:11], name: "山田花子", updated_at: ~N[2022-02-08 14:18:11] } ]

Slide 13

Slide 13 text

Ectoの構成要素 ✅ Ecto.Repo ✅ Ecto.Schema Ecto.Changeset Ecto.Query

Slide 14

Slide 14 text

Ecto.Changeset Changesets allow filtering, casting, validation and definition of constraints when manipulating structs. Changesetでデータのフィルタリング、キャスト(変換)、バリデーション、制約の定義が行える a set of change:一連の変更 をまとめたもの と捉えるといいかも 個人的には: Ectoを使った実装をする上で超重要 [1] [2] 1. https://hexdocs.pm/ecto/Ecto.Changeset.html 2. https://stackoverflow.com/a/33186341

Slide 15

Slide 15 text

Ecto.Changeset データに対する変更内容が1つの構造体にまとまっている action: 変更の種類( :insert | :update | :delete | :replace | :ignore ) data: 変更を加える元データ errors: 変更を加えようとした際に発生したエラー changes: 加えた変更 valid?: 有効な変更かどうか ` ` iex(1)> User.changeset(%User{}, %{name: "koga", age: "invalid"}) #Ecto.Changeset< action: nil, changes: %{name: "koga"}, errors: [age: {"is invalid", [type: :integer, validation: :cast]}], data: #Sample.Accounts.User<>, valid?: false >

Slide 16

Slide 16 text

Ecto.Changeset あるデータに対する一連の制約をpipeで繋げて実装する 例. attrsからfiele1とfield2を取り出してsome_dataにマッピング(cast) field1が必須 field1が正規表現にマッチする field2が3より小さい def changeset(some_data, attrs) do some_data |> cast(attrs, [:field1, :field2]) |> validate_required([:field1]) |> validate_format(:field1, ~r/@/) |> validate_number(:field2, less_than: 3) end

Slide 17

Slide 17 text

Ecto.Changeset tips: 登録時と更新時でバリデーションの要件が違う場合は関数を分けるなんてこともできる data |> changeset化 |> バリデーション |> Repoに投げ込み という流れをイメージできると良さそう def changeset_for_insert(some_data, attrs) do # insert時のcast, validationを記述 end def changeset_for_update(some_data, attrs) do # update時のcast, validationを記述 end

Slide 18

Slide 18 text

Ectoの構成要素 ✅ Ecto.Repo ✅ Ecto.Schema ✅ Ecto.Changeset Ecto.Query

Slide 19

Slide 19 text

Ecto.Query QueryのDSLを提供するもの Elixirコードでクエリを書くためのマクロ集と捉えるとよい 例. Elixirコード: 生成されるクエリ: Comment |> join(:inner, [c], p in Post, on: c.post_id == p.id) |> select([c, p], {p.title, c.text}) SELECT p1."title", c0."text" FROM "comments" AS c0 INNER JOIN "posts" AS p1 ON c0."post_id" = p1."id" []

Slide 20

Slide 20 text

Ecto.Query Keywordで書くことも、pipe operatorで書くこともできる チームでの決めの問題か? 個人的にはpipeで書く派 from u in "users", where: u.age > 18, select: u.name "users" |> where([u], u.age > 18) |> select([u], u.name)

Slide 21

Slide 21 text

まとめ Ecto: "the database wrapper and query generator for Elixir" Ecto.Repo, Ecto.Schema, Ecto.Changeset, Ecto.Queryの4つが主な構成要素 Ecto.Repo: data storeへの接続、やりとりの定義(Adapter) Ecto.Schema: データをElixirの構造体にマップ Ecto.Changeset: データの一連の変換・バリデーション Ecto.Query: クエリのDSLを提供 Changesetを自由自在に書けると多種多様な要件の実装が可能になる なんらか特殊なバリデーションを組んだり has_manyの構造を同一トランザクションで保存したり cf. cast_assoc/3 , put_assoc/4 DBなしでEctoを使うなどの応用もあり おまけ: @the_haigo さんにLTいただく Ecto.Multi の話も非常に重要 ` ` ` ` ` `