$30 off During Our Annual Pro Sale. View Details »

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

shozo koga
February 24, 2022

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

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

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を学ぼうの会

    View Slide

  2. About Me
    @koga1020_

    @koga1020

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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: "[email protected]")

    View Slide

  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)

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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]

    }

    ]

    View Slide

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

    View Slide

  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

    View Slide

  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

    >

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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" []

    View Slide

  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)

    View Slide

  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 の話も非常に重要
    ` ` ` `
    ` `

    View Slide