Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ectoの全体感をまとめてみる

302e60b4d42787b4a39de4a3a80cfc9d?s=47 shozo koga
February 24, 2022

 Ectoの全体感をまとめてみる

2022/02/24 fukuoka.ex#51:Elixirお茶会 での登壇資料です

302e60b4d42787b4a39de4a3a80cfc9d?s=128

shozo koga

February 24, 2022
Tweet

More Decks by shozo koga

Other Decks in Programming

Transcript

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

  2. About Me @koga1020_ @koga1020 koga1020.com 👨‍💻 自己紹介 古賀 祥造(koga1020) 福岡在住のバックエンドエンジニア(最近はマネジメント寄り)

    fukuoka.ex 管理人 💡 最近の興味関心 Elixir・Phoenixを使ったWebアプリケーション開発 マイクロサービスの実現。実装パターンの学習 美味しいご飯・美味しいお酒 🍶 マイホームで快適に過ごすこと 🏠
  3. fukuoka.exについて @piacereさんが2017年に発足 🎉 2018年3月からkoga1020がjoin。オフライン会場準備などイベント開催のお手伝い 2020年1月からすべてオンライン開催に およそ月1の頻度でイベントを開催しています

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

  5. 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
  6. たとえば PostgreSQLを使っている場合: MySQLを使っている場合: 異なるデータベースを使っていても、DB操作は抽象化して扱うことができる defp deps do [ {:postgrex, "~>

    0.15"} ] end defp deps() do [ {:myxql, "~> 0.6.0"} ] end iex> Repo.get_by(User, email: "foo@example.com")
  7. ざっくりいうと 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)
  8. Ectoの構成要素 Ecto.Repo Ecto.Schema Ecto.Changeset Ecto.Query

  9. 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
  10. Ectoの構成要素 ✅ Ecto.Repo Ecto.Schema Ecto.Changeset Ecto.Query

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

    さんのLTで聞けるはず! ` `
  12. 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] } ]
  13. Ectoの構成要素 ✅ Ecto.Repo ✅ Ecto.Schema Ecto.Changeset Ecto.Query

  14. 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
  15. 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 >
  16. 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
  17. 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
  18. Ectoの構成要素 ✅ Ecto.Repo ✅ Ecto.Schema ✅ Ecto.Changeset Ecto.Query

  19. 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" []
  20. 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)
  21. まとめ 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 の話も非常に重要 ` ` ` ` ` `