ElixirのMnesiaラッパー、Amnesiaを使ってみる

　Elixirで手軽にDBを使うにはMnesiaがよいらしい。しかもAmnesiaというラッパーを使うともっと手軽に始められるということです。早速使ってみましょう。以下のサイトを参考にしました。

[「amnesia - mnesia wrapper for Elixir」](https://github.com/meh/amnesia)
[「Store Everything With Elixir and Mnesia」](https://code.tutsplus.com/articles/store-everything-with-elixir-and-mnesia--cms-29821)
[「Using mnesia database from Elixir」](https://www.amberbit.com/elixir-cocktails/elixir/using-mnesia-database-from-elisir/)


　まずプロジェクトを作成します。

```sh
mix new amnesia_test
```

　次にdepsにamnesiaを追加します。

```elixir:mix.exs
#
defp deps do
    [{:amnesia, "~> 0.2.7"}]
end
#
```

　インストールします。

```sh
mix deps.get
```


　lib/Database.exにdatabase/tableの定義を行います。Databaseはいくつかのテーブルとhelper関数をグループ化したものです。githubの公式のexampleをそのまま掲載します。コメントがたくさんついていますので、自分なりに翻訳してみました。コメントを読めばだいたい理解できるでしょうが、少し説明を加えておきます。このdatabaseはDatabaseという名前で作られ、UserとMessageという2つのテーブルと小さなhelper関数を定義しています。


```elixir:lib/Database.ex
# defdatabaseや他のmacrosを使えるようにする。
use Amnesia

# defdatabase macroでdatabaseを定義する。Databaseと名付けます。
# defdatabaseは基本的にdefmoduleを拡張したものです。
defdatabase Database do
  # Message関数で参照するUserテーブル名をあらかじめ宣言しておきます。
  # ここでは宣言だけで、実際の定義は下の方で行います。
  deftable User

  #  user_id key と content　の2つの属性を持つMessageテーブルを定義します。タイプはbagです。
  deftable Message, [:user_id, :content], type: :bag do
    # これは必要ではないですが、typeをより厳密に定義してくれます。
    @type t :: %Message{user_id: integer, content: String.t}

    # helper関数です。Message recordからuserを取り出します
    def user(self) do
      User.read(self.user_id)
    end

    # 上のdirtyバージョンです
    def user!(self) do
      User.read!(self.user_id)
    end
  end

  # ユーザの属性を記述したUserテーブルの定義です。タイプはordered_setです。
  # emailのインデックスを作ります。検索速度が向上します。
  deftable User, [{ :id, autoincrement }, :name, :email], type: :ordered_set, index: [:email] do
    # 対応の宣言は必ずしも必要はありませんが、あった方が良いです。
    @type t :: %User{id: non_neg_integer, name: String.t, email: String.t}

    # helper関数です。userへのmessageを追加します, using write
    # Message.writeは引数をMessageテーブルに書き込みます。
    def add_message(self, content) do
      %Message{user_id: self.id, content: content} |> Message.write
    end

    #  helper関数です。add_messageのdirtyバージョンです。
    # 上でwriteを使う代わりに、ここではwrite!を使っています。
    def add_message!(self, content) do
      %Message{user_id: self.id, content: content} |> Message.write!
    end

    #  helper関数です。特定ユーザの全メッセージを取得します。
    def messages(self) do
      Message.read(self.id)
    end

    # helper関数です。messagesのdirtyバージョンです
    def messages!(self) do
      Message.read!(self.id)
    end
  end
end
```


　上のテーブル定義ですが、Messageテーブルはtypeをbagとして、Userテーブルはtypeをordered_setとして定義されています。　一般にmnesiaのテーブルは以下の3つのtypeに分かれます。

```
:set - デフォルトタイプです。 primary keyでユニーク性が保たれ、
       同じprimary keyを持つrowは複数存在しません。またrowはソートされません。

:ordered_set　- :set と同じですが、primary keyでソートされます。

:bag　- 同じprimary keyを持つrowが複数存在しえます。但し全く同じrowは存在しません。
```



　次のコマンドでデータベースを作成します。 --disk ですからカレントnodeのディスクへデータが保存されます。--memoryを指定すればメモリに保存されます。

```sh
mix amnesia.create -d Database --disk
```

　ちなみに、データベースを削除するには以下のコマンドを打ちます。

```sh
mix amnesia.drop -d Database
```


　さてデータベースを作成したらiexを立ち上げてみましょう。上で定義したDatabaseを使うためにuse Databaseを行います。


```sh
$ iex -S mix
iex(1)> use Database
[Amnesia, Amnesia.Fragment, Exquisite, Database, Database.User, Database.User,
 Database.Message, Database.Message]
```

　UserテーブルにRichardを追加します。User.write!で書き込んでいます。

```sh
iex(2)> richard = %User{name: "Richard", email: "richard@example.com"} |>User.write!
%Database.User{email: "richard@example.com", id: 1, name: "Richard"}
```

　MessageテーブルにRichardへのメッセージを3個書きます。User.add_message!というhelper関数を使っています。

```sh
iex(3)>  richard |> User.add_message!("aaaaaaaaaa")
%Database.Message{content: "aaaaaaaaaa", user_id: 1}
iex(4)> richard |> User.add_message!("bbbbbbbbbbb")
%Database.Message{content: "bbbbbbbbbbb", user_id: 1}
iex(5)> richard |> User.add_message!("ccccccccccc")
%Database.Message{content: "ccccccccccc", user_id: 1}
```

　Richardのメッセージをすべて読みます。User.messages!というhelper関数を使っています。

```sh
iex(6)> User.messages!(richard)
[
  %Database.Message{content: "aaaaaaaaaa", user_id: 1},
  %Database.Message{content: "bbbbbbbbbbb", user_id: 1},
  %Database.Message{content: "ccccccccccc", user_id: 1}
]
```

　ここで一度iexを終了して、再度立ち上げます。


```sh
[root@www13134uf amnesia_test]# iex -S mix
iex(1)> use Database
[Amnesia, Amnesia.Fragment, Exquisite, Database, Database.User, Database.User,
 Database.Message, Database.Message]
```

　Richard を取得します。データベースはディスクに保存してあるので無事読み込めます。

```sh
iex(2)> richard = User.read!(1)
%Database.User{email: "richard@example.com", id: 1, name: "Richard"}
```

　Richard のメッセージを取得します。やはりデータは残っていました。めでたしめでたしです。

```sh
iex(3)> User.messages!(richard)
[
  %Database.Message{content: "aaaaaaaaaa", user_id: 1},
  %Database.Message{content: "bbbbbbbbbbb", user_id: 1},
  %Database.Message{content: "ccccccccccc", user_id: 1}
]
```

　この2つの操作をmodule/function化しておきましょう。Reader.exを作成します。

```elixir:lib/Reader.ex
defmodule Reader do
  use Database
  def getMess(id) do
    p = User.read!(id)
    User.messages!(p)
  end
end
```

　iexを立ち上げて、Reader.getMess(1)を叩くだけで結果が出力できるようになりました。

```sh
$ iex -S mix
iex(1)> Reader.getMess(1)
[
  %Database.Message{content: "aaaaaaaaaa", user_id: 1},
  %Database.Message{content: "bbbbbbbbbbb", user_id: 1},
  %Database.Message{content: "ccccccccccc", user_id: 1}
]
```



　以上で終わりです。