【Railsエンジニア向き】Phoenixでコンソールからモデル操作をやってみよう【ハンズオン】

 ## Elixirのコンソールで随分苦しんだので

Phoenixを触ってはみたものの、モデル操作辺りでコンソール操作がうまくいかず、すっかり打ちひしがれているそこのアナタ！

PhoenixもRailsのようにサクサク触ってみたいではなくないですか？

ただ・・・Phoenixのやつ、Railsコンソールみたいにサクサク実験できる段階になるまで少し前提条件が存在するようです。

実際、コンソールで挙動を試そうとしてググっても、何通りも書き方が出てきたり、そのとおりにやってみても言うこと聞かない事が多くて、コンソールの操作に本当にイライラします。

ここでは、そんな状況から脱するための前提条件をステップを踏んで紹介していきたいと思います。

## ここでやること

- アプリ立ち上げ
- モデル作成
- 新規データーの作成
- DBへの登録(insert)

## 前提条件

MacOS 12.3.1 (Apple M1)
Elixir 1.13.4 (compiled with Erlang/OTP 25)
Phoenix installer v1.6.10
psql (PostgreSQL) 14.2

特にバージョンの差が出る部分でもないとは思いますので、古すぎなければ良いかと思います。
本文中、bashとzshの表記が混在していますが、Macがzshをデフォルトとしている為、Phoenix側からのガイドメッセージはbash($)表記ですが、コマンドはzsh(%)で表記されています。

## ベースとなるアプリの作成

早速アプリを作成します。

```zsh
% mix phx.new phx_sample
```

でサクッと土台を作ります。

```
.
.
(略)
* running mix deps.compile

We are almost there! The following steps are missing:

    $ cd phx_sample

Then configure your database in config/dev.exs and run:

    $ mix ecto.create

Start your Phoenix app with:

    $ mix phx.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phx.server
```

メッセージの通り、

```zsh
% cd phx_sample
```

でプロジェクトフォルダに移動して、

```zsh
% mix ecto.create
```

をやっておきます。

## モデルの作成

モデルの作成ですが、 `phx.gen.schema` というコマンドを使って作成します。
(※ 本当は `phx.gen.context` で作った方がテスト用のファイル等も出来て都合良かったりしますが、今回は最低限で・・)

```zsh
（＊ これは打たない）
% mix phx.gen.schema Post posts title:string content:text
```

とすると、以下のレスポンスとなります。

```
* creating lib/phx_sample/post.ex
* creating priv/repo/migrations/xxxxxxxxxxxx_create_posts.exs

Remember to update your repository by running migrations:

    $ mix ecto.migrate

```

上記２ファイルが作成され、 `post.ex` 内に `changeset` という関数が作成されますので、モデル操作に必要なものは揃います。

上記コマンドでは以下のような `post.ex` が作成されます。


```elixir:lib/phx_sample/post.ex
（＊ これはサンプル）

defmodule PhxSample.Post do
  use Ecto.Schema
  import Ecto.Changeset

  schema "posts" do
    field :content, :string
    field :title, :string

    timestamps()
  end

  @doc false
  def changeset(post, attrs) do
    post
    |> cast(attrs, [:title, :content])
    |> validate_required([:title, :content])
  end
end
```

これだけでもモデル操作出来ますし、Railsのモデルと考えるのであれば、感覚として同じ状態ですが、Phoenixの場合、他のgenコマンドとの兼ね合いでネームスペースを作成しておくと良さそうです。

```zsh
% mix phx.gen.schema Posts.Post posts title:string content:text
```

このように、 `Posts.Post` として、 （同名である必要はありませんが）何か機能の括り的な命名で、ネームスペースを作成しておくと良いと思います。

すると、 以下のレスポンスとなり、少しモデルの作成場所が変わります。

```
* creating lib/phx_sample/posts/post.ex
* creating priv/repo/migrations/xxxxxxxxxxxx_create_posts.exs

Remember to update your repository by running migrations:

    $ mix ecto.migrate

```

そして、作成されるファイル内容は、

```elixir:lib/phx_sample/posts/post.ex
defmodule PhxSample.Posts.Post do
  use Ecto.Schema
  import Ecto.Changeset

  schema "posts" do
    field :content, :string
    field :title, :string

    timestamps()
  end

  @doc false
  def changeset(post, attrs) do
    post
    |> cast(attrs, [:title, :content])
    |> validate_required([:title, :content])
  end
end
```

このように １行目の

```elixir
defmodule PhxSample.Posts.Post do
```

部分だけ違って作成されます。

この影響で、モデルを呼び出す時の書き方が変わってきますので、操作で迷った際はモデルを見に行くようにすれば良いでしょう。

では、操作の前にマイグレーションを通しておきましょう。

```zsh
% mix ecto.migrate
```

として、

```
Compiling 1 file (.ex)
Generated phx_sample app

12:57:35.205 [info]  == Running xxxxxxxxxxx PhxSample.Repo.Migrations.CreatePosts.change/0 forward

12:57:35.207 [info]  create table posts

12:57:35.217 [info]  == Migrated xxxxxxxxxxxx in 0.0s
```

マイグレーションが通れば準備OKです。

## コンソールを試す

### まずはコンソールの起動

コンソールの起動は、

```zsh
% iex -S mix
```

で行います。

```elixir
Erlang/OTP 25 [erts-13.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

Interactive Elixir (1.13.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 
```

こんな感じで、iexが表示されればOKです。 `-S mix` とすることで、プロジェクトのモデルを操作する事が出来ています。

では、早速実験です。

`Ecto` に `Changeset.cast/3` という関数があります。

```elixir
Ecto.Changeset.cast(%PhxSample.Posts.Post{update時に元のデーターが入る}, %{入力したいデーター}, [アトリビュート]) 
```

となりますので、元のデーターがない新規作成の場合

```elixir
Ecto.Changeset.cast(%PhxSample.Posts.Post{}, %{ title: "", content: "" }, [:title, :content]) 
```

このようにすれば、作成済みのモデルを操作するための構造体が作成できます。

```elixir
iex(1)> Ecto.Changeset.cast(%PhxSample.Posts.Post{}, %{ title: "", content: "" }, [:title, :content]) 
#Ecto.Changeset<action: nil, changes: %{}, errors: [],
 data: #PhxSample.Posts.Post<>, valid?: true>
```

### changeset

ただ、ここまで来ると毎回打つのが大変です。

もちろんアトリビュート等の部分は、すでにモデルに実装されておりますので、省略された記法が使えます。

```elixir:lib/phx_sample/posts/post.ex
defmodule PhxSample.Posts.Post do
  use Ecto.Schema
  import Ecto.Changeset

  schema "posts" do
    field :content, :string
    field :title, :string

    timestamps()
  end

  @doc false
  def changeset(post, attrs) do
    post
    |> cast(attrs, [:title, :content])
    |> validate_required([:title, :content])
  end
end
```

ここに記述されている、 `changeset/2` を使います。
この `changeset/2`関数は、

```elixir
import Ecto.Changeset
```

の記述によって、 `Changeset.cast/3` が `PhxSample.Posts.Post`　内で使用可能となっています。

```elixir
  def changeset(post, attrs) do
    post
    |> cast(attrs, [:title, :content])
    |> validate_required([:title, :content])
  end
```

関数内部では

```elixir
    post
    |> cast(attrs, [:title, :content])
```

このように使われていますね。

パイプ演算子を使って記述しているので、第一引数が見当たらなくなっていますね。
パイプ演算子の場合、本来の第一引数は、 `post` 部分となっています。
書き順が変わるので、慣れるまでなかなか覚えづらい箇所です。

この関数を使って書き直すと、

```elixir
PhxSample.Posts.Post.changeset(%PhxSample.Posts.Post{}, %{})
```

または、データーを空文字で入れるなら

```elixir
PhxSample.Posts.Post.changeset(%PhxSample.Posts.Post{}, %{title: "", content: ""})
```
こうなります。試してみましょう。

```elixir
iex(2)> PhxSample.Posts.Post.changeset(%PhxSample.Posts.Post{}, %{title: "", content: ""})
#Ecto.Changeset<
  action: nil,
  changes: %{},
  errors: [
    title: {"can't be blank", [validation: :required]},
    content: {"can't be blank", [validation: :required]}
  ],
  data: #PhxSample.Posts.Post<>,
  valid?: false
>
```

結果は少し変わってきますが、これは `changeset/2` の内部で

```elixir
|> validate_required([:title, :content])
```

バリデーションを通すようになっている為です。

### alias

　ここまで来ると、 `PhxSample.Posts.Post` と書くのが煩わしいですね。

ただし、Railsとは違ってモデル名だけで呼び出しが出来ませんので、

```elixir
Post.changeset(%Post{}, %{title: "", content: ""})
```

こういう書き方をすると、

```elixir
iex(3)> Post.changeset(%Post{}, %{title: "", content: ""})                
** (CompileError) iex:18: Post.__struct__/1 is undefined, cannot expand struct Post. Make sure the struct name is correct. If the struct name exists and is correct but it still cannot be found, you likely have cyclic module usage in your code
```

エラーとなります。
Railsでは

```ruby
Post.new
```
のようにインスタンスを作る事が出来ていました。

近い操作性をPoenixで実現するのであれば、 `alias` を書いておくのが良いようです。

```elixir
alias PhxSample.Posts.Post
```

と書くことで、最後に記述されている `Post` を `PhxSample.Posts.Post` として使う事が出来ます。

```elixir
iex(4)> alias PhxSample.Posts.Post
PhxSample.Posts.Post
```

こうすれば、

```elixir
iex(5)> Post.changeset(%Post{}, %{title: "", content: ""})
#Ecto.Changeset<
  action: nil,
  changes: %{},
  errors: [
    title: {"can't be blank", [validation: :required]},
    content: {"can't be blank", [validation: :required]}
  ],
  data: #PhxSample.Posts.Post<>,
  valid?: false
>
```

随分シンプルになってきました。
`alias` さえ書いておけば、Railsの

```ruby
Post.new
```

のような感覚で、

```elixir
Post.changeset(%Post{}, %{})
```

と、書くことができました。

では、レコードさせてみましょう。

### insert

```elixir
changeset = Post.changeset(%Post{}, %{title: "テスト", content: "こんにちは"})
```

先に変数にchangesetを作成します。

```elixir
iex(6)> changeset = Post.changeset(%Post{}, %{title: "テスト", content: "こんにちは"})  
#Ecto.Changeset<
  action: nil,
  changes: %{content: "こんにちは", title: "テスト"},
  errors: [],
  data: #PhxSample.Posts.Post<>,
  valid?: true
>
```

これを、登録するには、

```elixir
PhxSample.Repo.insert(changeset)  
```

このように書きます。

```elixir
iex(7)> PhxSample.Repo.insert(changeset)          
[debug] QUERY OK db=2.6ms queue=14.8ms idle=1698.4ms
INSERT INTO "posts" ("content","title","inserted_at","updated_at") VALUES ($1,$2,$3,$4) RETURNING "id" ["こんにちは", "テスト", ~N[2022-07-12 05:19:13], ~N[2022-07-12 05:19:13]]
↳ :erl_eval.do_apply/7, at: erl_eval.erl:744
{:ok,
 %PhxSample.Posts.Post{
   __meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
   content: "こんにちは",
   id: 4,
   inserted_at: ~N[2022-07-12 05:19:13],
   title: "テスト",
   updated_at: ~N[2022-07-12 05:19:13]
 }}
```

登録できました。

ここまでの処理も、alias使ってもう少し書き直すと、


```elixir
iex(8)> alias PhxSample.Repo            
PhxSample.Repo
iex(9)> Repo.insert(changeset)                                                       [debug] QUERY OK db=2.9ms idle=639.5ms
INSERT INTO "posts" ("content","title","inserted_at","updated_at") VALUES ($1,$2,$3,$4) RETURNING "id" ["こんにちは", "テスト", ~N[2022-07-12 05:22:37], ~N[2022-07-12 05:22:37]]
↳ :erl_eval.do_apply/7, at: erl_eval.erl:744
{:ok,
 %PhxSample.Posts.Post{
   __meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
   content: "こんにちは",
   id: 5,
   inserted_at: ~N[2022-07-12 05:22:37],
   title: "テスト",
   updated_at: ~N[2022-07-12 05:22:37]
 }}
```

このようになりますし、パイプ演算子を使うと、一旦変数を置かなくても

```elixir
Post.changeset(%Post{}, %{title: "テスト", content: "こんにちは"}) |> Repo.insert
```

このように書けますので、

```elixir
iex(10)> Post.changeset(%Post{}, %{title: "テスト", content: "こんにちは"}) |> Repo.insert
[debug] QUERY OK db=4.7ms queue=0.1ms idle=1254.3ms
INSERT INTO "posts" ("content","title","inserted_at","updated_at") VALUES ($1,$2,$3,$4) RETURNING "id" ["こんにちは", "テスト", ~N[2022-07-12 05:24:29], ~N[2022-07-12 05:24:29]]
↳ :erl_eval.do_apply/7, at: erl_eval.erl:744
{:ok,
 %PhxSample.Posts.Post{
   __meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
   content: "こんにちは",
   id: 6,
   inserted_at: ~N[2022-07-12 05:24:29],
   title: "テスト",
   updated_at: ~N[2022-07-12 05:24:29]
 }}
```

Railsコンソールのような感覚で、コマンド履歴を利用して少しずつ結果を見なから実験できるようになりますね。

だいぶ扱いやすくなったのではないでしょうか？

## まとめ

- コンソールを使うときは alias を使って記述を短縮
- パイプ演算子を使うと、処理の順番通りに記述出来るので、実験しやすくなる

次回は、呼び出し・編集・削除等も紹介します。