Elixir Ecto Association

**(追記)本記事は2022/10/23 に更新しました**

**Ecto** にはテーブル同士を関連付ける **Ecto Association** の機能があります。関連付けには以下の３つのタイプが考えられます。

* **one-to-one**
* **one-to-many**
* **many-to-many**


**Ecto Association** は以下の公式サイトで説明されています。今回はこのガイドを実際に実行し、補足説明を加えていきたいと思います。
[Ecto Association Guide](https://hexdocs.pm/ecto/2.2.11/associations.htm)




# ０．Ectoの設定


まずは Ecto の設定を、実際にプロジェクトを作成しながら行っていきます。mix で ecto_assoc というプロジェクトを作成します。オプションの **--sup** はこのアプリケーションが **supervision tree** を持っていることを示しています。

```
mix new ecto_assoc --sup
```

プロジェクトディレクトリに移ります。
```
cd ecto_assoc
```

次に必要なパッケージをインストールします。以下のファイルのdepsを変更します。**postgrex** はPostgreSQLのドライバです。
```elixir:mix.exs
defp deps do
  [{:ecto, "~> 2.0"},
   {:postgrex, "~> 0.11"}]
end
```


インストールします。

```sh:
mix deps.get
```

まず以下のコマンドで **Repo (lib/ecto_assoc/repo.ex)**を作成します。


```sh:lib/ecto_assoc/repo.ex
mix ecto.gen.repo -r EctoAssoc.Repo
```

**repo.ex** を確認します。**EctoAssoc.Repo** はデータベースへの query に使われます。ここでは**Ecto.Repo** を使うことが宣言されています。**otp_app** はどのアプリがデータベースへアクセスしているかを教えています。ここでは **:ecto_assoc** となっているのでこのプロジェクトの **config.exs** を参照し、その接続情報をもとにデータベースにアクセスします。

```elixir:lib/ecto_assoc/repo.ex
defmodule EctoAssoc.Repo do
  use Ecto.Repo, otp_app: :ecto_assoc
end
```

さらに **config/config.exs** の DB 設定を確認します。上で指定した **:ecto_assoc** の設定です。必要であれば、username/password などを自分の環境に合わせます。最後の行は、アプリケーションに対して repo について教えます。

```elixir:config/config.exs
config :ecto_assoc, EctoAssoc.Repo,
  adapter: Ecto.Adapters.Postgres,
  database: "ecto_assoc_repo",
  username: "user",
  password: "pass",
  hostname: "localhost"

config :ecto_assoc, ecto_repos: [EctoAssoc.Repo]
```

次に lib/ecto_assoc/application.ex を編集します。**EctoAssoc.Repo** を **application** の **supervision tree** の中の **supervisor** としてセットします。これによって、Ecto process を起動して、アプリのqueryを受け取り実行できるようになります。

```elixir:lib/ecto_assoc/application.ex
#
  import Supervisor.Spec
  def start(_type, _args) do
    
    children = [
      supervisor(EctoAssoc.Repo, [])
    ]
#
```

最後に次のコマンドで DB を作成します。
```
 mix ecto.create
```

# １．One-to-one  (User-Avatar)

まず **One-to-one** の **Ecto Association** を見ていきます。まず **usersテーブル**と **avatarsテーブル**を作成します。次に両テーブルに関連性を与えます。最後に実際の実行結果を確認したいと思います。

## １－１．User

usersテーブルを作成する **migrations** を作ります。**migrations とは Elixir の記述だけでデータベースのテーブル操作をしてしまおうとするものです。**

```
mix ecto.gen.migration create_user
```

以下のような空の **migrations** が作成されます。
```elixir:priv/repo/migrations/20221023071420_create_user.exs
efmodule EctoAssoc.Repo.Migrations.CreateUser do
  use Ecto.Migration

  def change do

  end
end
```

これを以下のように修正します。

```elixir:priv/repo/migrations/20221023071420_create_user.exs
defmodule EctoAssoc.Repo.Migrations.CreateUser do
  use Ecto.Migration

  def change do
    create table(:users) do
      add :name, :string
      add :email, :string
    end
  end
end
```

User schemaを作成します

```elixir:lib/ecto_assoc/user.ex
defmodule EctoAssoc.User do
  use Ecto.Schema

  schema "users" do
    field :name, :string
    field :email, :string
  end
end
```


## １－２．Avatar

avatars テーブルを作成する **migrations** を作ります。

```
mix ecto.gen.migration create_avatar
```


作成されたmigrationsを以下のように修正します。

```elixir:priv/repo/migrations/20221023072408_create_avatar.exs
defmodule EctoAssoc.Repo.Migrations.CreateAvatar do
  use Ecto.Migration

  def change do
    create table(:avatars) do
      add :nick_name, :string
      add :pic_url, :string
    end
  end
end
```

Avatar schemaを作成します

```elixir:lib/ecto_assoc/avatar.ex
defmodule EctoAssoc.Avatar do
  use Ecto.Schema

  schema "avatars" do
    field :nick_name, :string
    field :pic_url, :string
  end
end
```

以上で準備が整いましたので、DBとテーブルを作成したいと思います。

```
mix ecto.create
mix ecto.migrate
```


## １－３．Associationsを追加

**avatars** テーブルを変更する **migration** を作成します。

```
mix ecto.gen.migration avatar_belongs_to_user
```

どのように変更するかを記述します。ここでは **user_id** を **users** テーブルを参照する外部キーとして追加するように変更します。

```elixir:priv/repo/migrations/20221023073258_avatar_belongs_to_user.exs
defmodule EctoAssoc.Repo.Migrations.CreateAvatar do
  use Ecto.Migration

  def change do
    alter table(:avatars) do
      add :user_id, references(:users)
    end
  end
end
```


**Avatar schema** を修正（追加）します。ここでは **belongs_to field** を追加します。これにより **avatar.user** で **users テーブル**にアクセスできるようになります。

```elixir:lib/ecto_assoc/avatar.ex
defmodule EctoAssoc.Avatar do
  use Ecto.Schema

  schema "avatars" do
    field :nick_name, :string
    field :pic_url, :string
    belongs_to :user, EctoAssoc.User  # 追加
  end
end
```

**User schema** を修正（追加）します。これによる **usersテーブル**の変更はありませんが、**user.avatar** によって **avatars テーブル**へアクセスできるようになります。

```elixir:lib/ecto_assoc/user.ex
defmodule EctoAssoc.User do
  use Ecto.Schema

  schema "users" do
    field :name, :string
    field :email, :string
    has_one :avatar, EctoAssoc.Avatar  # 追加
  end
end
```

**migrate** を実行して、実際にテーブルを変更します。

```
mix ecto.migrate
```


## １－４．実行結果の確認

```elixir:
iex -S mix

### Repo, User, Avatarモジュール名の短縮でアクセス可能にします。
iex(1)> alias EctoAssoc.{Repo, User, Avatar}
[EctoAssoc.Repo, EctoAssoc.User, EctoAssoc.Avatar]


### Userを、Avater無しで、作成します。
iex(2)> user = %User{name: "John Doe", email: "john.doe@example.com"}
%EctoAssoc.User{
  __meta__: #Ecto.Schema.Metadata<:built, "users">,
  avatar: #Ecto.Association.NotLoaded<association :avatar is not loaded>,
  email: "john.doe@example.com",
  id: nil,
  name: "John Doe"
}

### Userをテーブルに挿入します。insert!(ビックリマーク)は例外を発生します。
### この時点で初めてUserはテーブルで実体化され、idが決まります。
iex(3)> user = Repo.insert!(user)
%EctoAssoc.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  avatar: #Ecto.Association.NotLoaded<association :avatar is not loaded>,
  email: "john.doe@example.com",
  id: 1,
  name: "John Doe"
}


### Avaterを作成します。
iex(4)> avatar = %Avatar{nick_name: "Elixir", pic_url: "http://elixir-lang.org/images/logo.png"}
%EctoAssoc.Avatar{
  __meta__: #Ecto.Schema.Metadata<:built, "avatars">,
  id: nil,
  nick_name: "Elixir",
  pic_url: "http://elixir-lang.org/images/logo.png",
  user: #Ecto.Association.NotLoaded<association :user is not loaded>,
  user_id: nil
}


### Userを、Avater付きで、作成します。
### schemaで追加した「has_one :avatar」に値を指定する形です。
iex(5)> user = %User{name: "John Doe", email: "john.doe@example.com", avatar: avatar}
%EctoAssoc.User{
  __meta__: #Ecto.Schema.Metadata<:built, "users">,
  avatar: %EctoAssoc.Avatar{
    __meta__: #Ecto.Schema.Metadata<:built, "avatars">,
    id: nil,
    nick_name: "Elixir",
    pic_url: "http://elixir-lang.org/images/logo.png",
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: nil
  },
  email: "john.doe@example.com",
  id: nil,
  name: "John Doe"
}


### Userをテーブルに挿入します。
### Userがid=2で作成され、その中のid=1のAVaterがuser_id=2で作られているように見えるのがわかります。
iex(6)>  user = Repo.insert!(user)
%EctoAssoc.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  avatar: %EctoAssoc.Avatar{
    __meta__: #Ecto.Schema.Metadata<:loaded, "avatars">,
    id: 1,
    nick_name: "Elixir",
    pic_url: "http://elixir-lang.org/images/logo.png",
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 2
  },
  email: "john.doe@example.com",
  id: 2,
  name: "John Doe"
}


### Userテーブルのエントリーを表示します。
### %User structの association(has_one :avatar)である :avatar をpreloadします。
### id=2のUserの中に埋め込まれる形でAvaterがpreloadされます。
iex(7)> Repo.all(User) |> Repo.preload(:avatar)
[
  %EctoAssoc.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    avatar: nil,
    email: "john.doe@example.com",
    id: 1,
    name: "John Doe"
  },
  %EctoAssoc.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    avatar: %EctoAssoc.Avatar{
      __meta__: #Ecto.Schema.Metadata<:loaded, "avatars">,
      id: 1,
      nick_name: "Elixir",
      pic_url: "http://elixir-lang.org/images/logo.png",
      user: #Ecto.Association.NotLoaded<association :user is not loaded>,
      user_id: 2
    },
    email: "john.doe@example.com",
    id: 2,
    name: "John Doe"
  }
]
```


# ２．One-to-many  (User-Post)

　次に **One-to-many** の **Ecto Association** を見ていきます。まず **usersテーブル**と **postsテーブル**を作成します。usersテーブルは前のものを再利用しますので、実際作るのはpostsのみになります。


## ２－１．Post

**posts テーブル**を作成する **migrations** を作ります。

```
mix ecto.gen.migration create_post
```


作成された **migrations** を以下のように修正します。

```elixir:priv/repo/migrations/20221023092809_create_post.exs
defmodule EctoAssoc.Repo.Migrations.CreatePost do
  use Ecto.Migration

  def change do
    create table(:posts) do
      add :header, :string
      add :body, :string
    end
  end
end
```

**Post schema** を作成します

```elixir:lib/ecto_assoc/post.ex
defmodule EctoAssoc.Post do
  use Ecto.Schema

  schema "posts" do
    field :header, :string
    field :body, :string
  end
end
```

以上で準備が整いましたので、テーブルを作成したいと思います。

```
mix ecto.migrate
```



## ２－２．Associationsを追加

**postsテーブル**を変更する **migration** を作成します。

```
mix ecto.gen.migration post_belongs_to_user
```

どのように変更するかを記述します。ここでは **user_id** を **users** テーブルを参照する外部キーとして追加するように変更します。

```elixir:priv/repo/migrations/20221023093443_post_belongs_to_user.exs
defmodule EctoAssoc.Repo.Migrations.PostBelongsToUser do
  use Ecto.Migration

  def change do
    alter table(:posts) do
      add :user_id, references(:users)
    end
  end
end
```


**Post schema** を修正（追加）します。ここでは **belongs_to field** を追加します。これにより **post.user** で **users テーブル**にアクセスできるようになります。

```elixir:lib/ecto_assoc/post.ex
defmodule EctoAssoc.Post do
  use Ecto.Schema

  schema "posts" do
    field :header, :string
    field :body, :string
    belongs_to :user, EctoAssoc.User  # 追加
  end
end
```

**User schema** を修正（追加）します。これによる **usersテーブル**の変更はありませんが、**user.posts** によって **posts テーブル**へアクセスできるようになります。

```elixir:lib/ecto_assoc/user.ex
defmodule EctoAssoc.User do
  use Ecto.Schema

  schema "users" do
    field :name, :string
    field :email, :string
    has_many :posts, EctoAssoc.Post  # 追加
  end
end
```

**migrate** を実行して、実際にテーブルを変更します。

```
mix ecto.migrate
```



## ２－３．【オマケ】psqlコマンドでテーブルを確認

　ここまでのDBの様子を、psqlコマンドで確認しておきます。Ecto.Repoを通さずに、生のテーブルも確認しておきたいと思います。


```
### psqlを起動します。
sudo -u postgres psql

### 以下のコマンドでDB名 ecto_assoc_repo を確認します。
postgres=# \l

### DB ecto_assoc_repo に接続します。
postgres=# \c ecto_assoc_repo

### テーブル一覧を表示します。
ecto_assoc_repo=# \z
                                  Access privileges
 Schema |       Name        |   Type   | Access privileges | Column access privi
leges
--------+-------------------+----------+-------------------+--------------------
------
 public | avatars           | table    |                   |
 public | avatars_id_seq    | sequence |                   |
 public | posts             | table    |                   |
 public | posts_id_seq      | sequence |                   |
 public | schema_migrations | table    |                   |
 public | users             | table    |                   |
 public | users_id_seq      | sequence |                   |
(7 rows)


### postsテーブルの詳細を表示します。
ecto_assoc_repo=# \d posts
                                 Table "public.posts"
 Column  |          Type          |                     Modifiers

---------+------------------------+---------------------------------------------
-------
 id      | bigint                 | not null default nextval('posts_id_seq'::reg
class)
 header  | character varying(255) |
 body    | character varying(255) |
 user_id | bigint                 |
Indexes:
    "posts_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "posts_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)

```


## ２－４．実行結果の確認

```elixir:
iex -S mix

### Repo, User, Postモジュール名の短縮でアクセス可能にします。
iex(1)> alias EctoAssoc.{Repo, User, Post}
[EctoAssoc.Repo, EctoAssoc.User, EctoAssoc.Post]


### Userを、Post無しで、作成します。
iex(2)> user = %User{name: "John Doe", email: "john.doe@example.com"}
%EctoAssoc.User{
  __meta__: #Ecto.Schema.Metadata<:built, "users">,
  email: "john.doe@example.com",
  id: nil,
  name: "John Doe",
  posts: #Ecto.Association.NotLoaded<association :posts is not loaded>
}


### Userをテーブルに挿入します。insert!(ビックリマーク)は例外を発生します。
### この時点で初めてUserはテーブルで実体化され、idが決まります。
iex(3)> user = Repo.insert!(user)
%EctoAssoc.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  email: "john.doe@example.com",
  id: 1,
  name: "John Doe",
  posts: #Ecto.Association.NotLoaded<association :posts is not loaded>
}


### postを、Userに関連付ける形で、作成します。Ecto.build_assoc/3 を使います。
### schemaで追加した「has_many :posts」に値を指定する形です。
### もちろん one_to_one で行った方法でも同じことが可能です。
iex(4)> post = Ecto.build_assoc(user, :posts, %{header: "Clickbait header", body: "No real content"})
%EctoAssoc.Post{
  __meta__: #Ecto.Schema.Metadata<:built, "posts">,
  body: "No real content",
  header: "Clickbait header",
  id: nil,
  user: #Ecto.Association.NotLoaded<association :user is not loaded>,
  user_id: 1
}


### postをテーブルに挿入します。insert!(ビックリマーク)は例外を発生します。
### この時点で初めてpostはテーブルで実体化され、idが決まります。user_idが関連付けられます。
iex(5)> Repo.insert!(post)
%EctoAssoc.Post{
  __meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
  body: "No real content",
  header: "Clickbait header",
  id: 1,
  user: #Ecto.Association.NotLoaded<association :user is not loaded>,
  user_id: 1
}


### ２つめのpostを、Userに関連付ける形で、作成します。
### schemaで追加した「has_many :posts」に値を指定する形です。
iex(6)> post = Ecto.build_assoc(user, :posts, %{header: "5 ways to improve your Ecto", body: "Add url of this tutorial"})
%EctoAssoc.Post{
  __meta__: #Ecto.Schema.Metadata<:built, "posts">,
  body: "Add url of this tutorial",
  header: "5 ways to improve your Ecto",
  id: nil,
  user: #Ecto.Association.NotLoaded<association :user is not loaded>,
  user_id: 1
}


### ２つめのpostをテーブルに挿入します。
### この時点で初めてpostはテーブルで実体化され、idが決まります。user_idが関連付けられます。
iex(7)> Repo.insert!(post)
%EctoAssoc.Post{
  __meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
  body: "Add url of this tutorial",
  header: "5 ways to improve your Ecto",
  id: 2,
  user: #Ecto.Association.NotLoaded<association :user is not loaded>,
  user_id: 1
}


### Userテーブルのエントリーを表示します。
### %User structの association(has_many :posts)である :posts をpreloadします。
### :postsは必要に応じて自動的にlazy-loadされることは無く、明示的にpreloadする必要があります。
### id=1のUserの中にpostsが埋め込まれる形になります。
iex(8)> Repo.get(User, user.id) |> Repo.preload(:posts)
%EctoAssoc.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  email: "john.doe@example.com",
  id: 1,
  name: "John Doe",
  posts: [
    %EctoAssoc.Post{
      __meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
      body: "No real content",
      header: "Clickbait header",
      id: 1,
      user: #Ecto.Association.NotLoaded<association :user is not loaded>,
      user_id: 1
    },
    %EctoAssoc.Post{
      __meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
      body: "Add url of this tutorial",
      header: "5 ways to improve your Ecto",
      id: 2,
      user: #Ecto.Association.NotLoaded<association :user is not loaded>,
      user_id: 1
    }
  ]
}
iex(9)>
```



# ３．Many-to-many  (Post-Tag)

　次に **Many-to-many** の **Ecto Association** を見ていきます。まず**postsテーブル**と**tagsテーブル**を作成します。postsテーブルは前のものを再利用しますので、実際作るのはtagsのみになります。


## ３－１．Tag

**tags テーブル**を作成する **migrations** を作ります。

```
mix ecto.gen.migration create_tag
```


作成された **migrations** を以下のように修正します。

```elixir:priv/repo/migrations/20180908055544_create_tag.exs
defmodule EctoAssoc.Repo.Migrations.CreateTag do
  use Ecto.Migration

  def change do
    create table(:tags) do
      add :name, :string
    end
  end
end
```

**Tag schema** を作成します

```elixir:lib/ecto_assoc/tag.ex
defmodule EctoAssoc.Tag do
  use Ecto.Schema

  schema "tags" do
    field :name, :string
  end
end
```

以上で準備が整いましたので、テーブルを作成したいと思います。

```
mix ecto.migrate
```



## ３－２．Associationsを追加

**Many-to-many** の関係を記述するためには、これまでのように既存のテーブルを変更するのではなく、新しいテーブル posts_tags を追加します。posts_tagsテーブルを作成するmigrationを作成します。

```
mix ecto.gen.migration create_posts_tags
```

どのように変更するかを記述します。

```elixir:priv/repo/migrations/20180908060734_create_posts_tags.exs
defmodule EctoAssoc.Repo.Migrations.CreatePostsTags do
  use Ecto.Migration

  def change do
    create table(:posts_tags) do
      add :tag_id, references(:tags)
      add :post_id, references(:posts)
    end

    create unique_index(:posts_tags, [:tag_id, :post_id])
  end
end
```


**Post schema** を修正（追加）します。

```elixir:lib/ecto_assoc/post.ex
defmodule EctoAssoc.Post do
  use Ecto.Schema

  schema "posts" do
    field :header, :string
    field :body, :string
    many_to_many :tags, EctoAssoc.Tag, join_through: "posts_tags"  # 追加
  end
end
```

**Tag schema** を修正（追加）します。

```elixir:lib/ecto_assoc/tag.ex
defmodule EctoAssoc.Tag do
  use Ecto.Schema

  schema "tags" do
    field :name, :string
    many_to_many :posts, EctoAssoc.Post, join_through: "posts_tags" # 追加
  end
end
```

**migrate** を実行して、実際にテーブルを追加します。

```
mix ecto.migrate
```





##３－３．実行結果の確認

```
iex -S mix

### Repo, Post、Tagモジュール名の短縮でアクセス可能にします。
iex(1)> alias EctoAssoc.{Repo, Tag, Post}
[EctoAssoc.Repo, EctoAssoc.Tag, EctoAssoc.Post]
```

まずTagとPostを作ります。

```
### Tagをテーブルに挿入します。 ３つのtag "clickbait"と"misc"、"ecto"を挿入します。
iex(2)> clickbait_tag = Repo.insert! %Tag{name: "clickbait"}
%EctoAssoc.Tag{
  __meta__: #Ecto.Schema.Metadata<:loaded, "tags">,
  id: 1,
  name: "clickbait",
  posts: #Ecto.Association.NotLoaded<association :posts is not loaded>
}

iex(3)> misc_tag = Repo.insert! %Tag{name: "misc"}
%EctoAssoc.Tag{
  __meta__: #Ecto.Schema.Metadata<:loaded, "tags">,
  id: 2,
  name: "misc",
  posts: #Ecto.Association.NotLoaded<association :posts is not loaded>
}

iex(4)> ecto_tag = Repo.insert! %Tag{name: "ecto"}
%EctoAssoc.Tag{
  __meta__: #Ecto.Schema.Metadata<:loaded, "tags">,
  id: 3,
  name: "ecto",
  posts: #Ecto.Association.NotLoaded<association :posts is not loaded>
}


### Postを作成します。
iex(5)> post = %Post{header: "Clickbait header", body: "No real content"}
%EctoAssoc.Post{
  __meta__: #Ecto.Schema.Metadata<:built, "posts">,
  body: "No real content",
  header: "Clickbait header",
  id: nil,
  tags: #Ecto.Association.NotLoaded<association :tags is not loaded>
}


### postをテーブルに挿入します。
iex(6)> post = Repo.insert!(post)
%EctoAssoc.Post{
  __meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
  body: "No real content",
  header: "Clickbait header",
  id: 1,
  tags: #Ecto.Association.NotLoaded<association :tags is not loaded>
}
```

以上でTagとPostを作りました。しかしAssociationsは作成していません。psqlで現状を確認してみましょう。

```
ecto_assoc_repo=# select * from tags;
 id |   name
----+-----------
  1 | clickbait
  2 | misc
  3 | ecto
(3 rows)

ecto_assoc_repo=# select * from posts;
 id |      header      |      body       | user_id
----+------------------+-----------------+---------
  1 | Clickbait header | No real content |
(1 row)

ecto_assoc_repo=# select * from posts_tags; # Associationsは空です。
 id | tag_id | post_id
----+--------+---------
(0 rows)
```

それではAssociationsを作成していきましょう。Ecto.Changesetを使いますので少し複雑になります。Ecto.Changesetを使うメリットは関連付けられたデータの変更に対して責任を持ってもらえることです。

```
### postをChangesetでラッピングします。
iex(7)> post_changeset = post |> Repo.preload(:tags) |> Ecto.Changeset.change
#Ecto.Changeset<action: nil, changes: %{}, errors: [], data: #EctoAssoc.Post<>,
 valid?: true>


### put_assoc は association を change として changeset の中に入れます。
### ここでassociationは以下のPost schemaを指します。
### Post schema : many_to_many :tags, EctoAssoc.Tag, join_through: "posts_tags"
iex(8)> post_with_tags = Ecto.Changeset.put_assoc(post_changeset, :tags, [clickbait_tag, misc_tag])
#Ecto.Changeset<
  action: nil,
  changes: %{
    tags: [
      #Ecto.Changeset<action: :update, changes: %{}, errors: [],
       data: #EctoAssoc.Tag<>, valid?: true>,
      #Ecto.Changeset<action: :update, changes: %{}, errors: [],
       data: #EctoAssoc.Tag<>, valid?: true>
    ]
  },
  errors: [],
  data: #EctoAssoc.Post<>,
  valid?: true
>
```

さて現状でもposts_tagsテーブルは空ですが、以下のコマンドでposts_tagsが更新されます。

```
iex(9)> post = Repo.update!(post_with_tags)
%EctoAssoc.Post{
  __meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
  body: "No real content",
  header: "Clickbait header",
  id: 1,
  tags: [
    %EctoAssoc.Tag{
      __meta__: #Ecto.Schema.Metadata<:loaded, "tags">,
      id: 1,
      name: "clickbait",
      posts: #Ecto.Association.NotLoaded<association :posts is not loaded>
    },
    %EctoAssoc.Tag{
      __meta__: #Ecto.Schema.Metadata<:loaded, "tags">,
      id: 2,
      name: "misc",
      posts: #Ecto.Association.NotLoaded<association :posts is not loaded>
    }
  ]
}
```

posts_tagsテーブルを確認すると確かにエントリーがあります。つまりPost schemaのmany_to_manyの定義から、posts_tagsテーブルにpost_id=1の2エントリー (tag_id=1, post_id=1)と(tag_id=2, post_id=1)が挿入されたのを確認できます。

```
ecto_assoc_repo=# select * from posts_tags;
 id | tag_id | post_id
----+--------+---------
  1 |      1 |       1
  2 |      2 |       1
(2 rows)
```



それではMany-manyの関係性を実際に確かめてみましょう。

```
iex(10)>  post = Repo.get(Post, post.id) |> Repo.preload(:tags)
%EctoAssoc.Post{
  __meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
  body: "No real content",
  header: "Clickbait header",
  id: 1,
  tags: [
    %EctoAssoc.Tag{
      __meta__: #Ecto.Schema.Metadata<:loaded, "tags">,
      id: 1,
      name: "clickbait",
      posts: #Ecto.Association.NotLoaded<association :posts is not loaded>
    },
    %EctoAssoc.Tag{
      __meta__: #Ecto.Schema.Metadata<:loaded, "tags">,
      id: 2,
      name: "misc",
      posts: #Ecto.Association.NotLoaded<association :posts is not loaded>
    }
  ]
}


iex(11)> post.header
"Clickbait header"
iex(12)> post.body
"No real content"
iex(13)> post.tags
[
  %EctoAssoc.Tag{
    __meta__: #Ecto.Schema.Metadata<:loaded, "tags">,
    id: 1,
    name: "clickbait",
    posts: #Ecto.Association.NotLoaded<association :posts is not loaded>
  },
  %EctoAssoc.Tag{
    __meta__: #Ecto.Schema.Metadata<:loaded, "tags">,
    id: 2,
    name: "misc",
    posts: #Ecto.Association.NotLoaded<association :posts is not loaded>
  }
]



iex(14)>  Enum.map(post.tags, & &1.name)
["clickbait", "misc"]
```

逆方向も確かめておきます。

```
iex(15)> tag = Repo.get(Tag, 1) |> Repo.preload(:posts)

%EctoAssoc.Tag{
  __meta__: #Ecto.Schema.Metadata<:loaded, "tags">,
  id: 1,
  name: "clickbait",
  posts: [
    %EctoAssoc.Post{
      __meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
      body: "No real content",
      header: "Clickbait header",
      id: 1,
      tags: #Ecto.Association.NotLoaded<association :tags is not loaded>
    }
  ]
}
```




以上で終わりますが、最後にオマケです。






# ４．【オマケ】ecto関連のコマンド

開発時は、以下のようにしてDBをクリアして、再度作り直すことができます。

```
mix ecto.drop
mix ecto.create
mix ecto.migrate
```

**mix help** でecto関連のコマンドを確認できます。英語で出力されていますが、まあ仕方ないですね。

```
# mix help | grep ecto
mix ecto               # Prints Ecto help information
mix ecto.create        # Creates the repository storage
mix ecto.drop          # Drops the repository storage
mix ecto.dump          # Dumps the repository database structure
mix ecto.gen.migration # Generates a new migration for the repo
mix ecto.gen.repo      # Generates a new repository
mix ecto.load          # Loads previously dumped database structure
mix ecto.migrate       # Runs the repository migrations
mix ecto.migrations    # Displays the repository migration status
mix ecto.rollback      # Rolls back the repository migrations
mix phx.new.ecto       # Creates a new Ecto project within an umbrella project
```


以上です。



**■ Elixir/Phoenixの基礎についてまとめた過去記事**
[Elixir Ecto チュートリアル - Qiita](https://qiita.com/sand/items/0861ad7a16f7ac612f41)
[Elixir Ecto のまとめ - Qiita](https://qiita.com/sand/items/71d0b35d74a4781f3564)
[Elixir Ecto Association - Qiita](https://qiita.com/sand/items/5581497972473e308f05)
[Phoenix1.6の基本的な仕組み - Qiita](https://qiita.com/sand/items/f42dc35c47589eab687e)