週刊Elixirライブラリ2015-9

Elixirのライブラリの解説を週ごとにお届けする記事です。
解説が間違っていた場合には、コメントか@hayabusa333にご連絡くださると嬉しいです。

今回はEctoについて

# Ecto is 何?
EctoはElixirにてデータベースとアクセスし、データを保存するためのライブラリです。
Ectoにてデータベースにアクセスするためにはアダプタが必要で、複数のアダプタに対応しております。対応アダプタは下記となります。

Database  Ecto Adapter      Dependency
PostgreSQL Ecto.Adapters.Postgres postgrex
MySQL   Ecto.Adapters.MySQL  mariaex
MSSQL   Tds.Ecto        tds_ecto
SQLite3   Sqlite.Ecto       sqlite_ecto

今回は、postgrexにてPostgreSQLにアクセスしようと思います。

# 実行環境
OS:OS X Yosemite
Erlang:Eshell V6.5, OTP-Version 18
Elixir:v1.0.4
PostgreSQL:v9.4.4

# Ectoの実行を行うための新規プロジェクトの作成
mix new 実行時に --sup をつけて実行した場合に mix.exs の内容が微妙に変わります。mix.exs の内容に注意してください。

$ mix new my_ecto --sup
$ cd my_ecto

# HexにてEctoをインストールするために設定ファイルの記載を行う

$ vim mix.exs

mix.exsの内容は下記となります。

defmodule MyEcto.Mixfile do
  use Mix.Project

  def project do
    [app: :my_ecto,
     version: "0.0.1",
     elixir: "~> 1.0",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     deps: deps]
  end

  # Configuration for the OTP application
  #
  # Type `mix help compile.app` for more information
  def application do
    [applications: [:logger, :postgrex, :ecto],
     mod: {MyEcto.App, []}]
     # mix new my_ecto --sup に --sup をつけると :mod の記載が追加される
     # 内容としては、起動時に実行するモジュールを指定しております。
     # 上記の記載がないとEctoがうまく起動しません
  end

  # Dependencies can be Hex packages:
  #
  #   {:mydep, "~> 0.3.0"}
  #
  # Or git/path repositories:
  #
  #   {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
  #
  # Type `mix help deps` for more examples and options
  defp deps do
    [
      {:postgrex, ">= 0.0.0"},
      {:ecto, "~> 0.14.3"}
    ]
  end
end

# EctoにてDBにアクセスするための設定を記載する

$ vim config/config.exs

config/config.exs の一番下に下記を追加してください

config :my_ecto, MyEcto.Repo, #アプリケーション名と、Repo名を記載
  adapter: Ecto.Adapters.Postgres, #使用するアダプタを指定
  database: "ecto_simple", #データベース名を設定
  username: "postgres", #ユーザー名を設定
  password: "password", #ユーザーがアクセスする際のパスワードを指定
  hostname: "localhost" #データベースサーバのIPアドレスを指定

# Ectoにてサーバにアクセスするための記述と実地するクエリをコードとして記載する

$ vim lib/my_ecto.ex

lib/my_ecto.ex の内容は以下となります。

# 起動時に読み込むモジュール
# 下記のstartメソッドがないとEctoが動いてくれないため注意が必要
defmodule MyEcto.App do
  use Application

  def start(_type, _args) do
    import Supervisor.Spec, warn: false
    tree = [worker(MyEcto.Repo, [])]
    opts = [strategy: :one_for_one, name: MyEcto.Supervisor]
    Supervisor.start_link(tree, opts)
  end

end

defmodule MyEcto.Repo do
  use Ecto.Repo,
    otp_app: :my_ecto
end

defmodule MyEcto.Weather do
  use Ecto.Model

  schema "weather" do
    field :city_id,  :integer # Defaults to type :string
    field :temp_lo, :integer
    field :temp_hi, :integer
    field :prcp,    :float, default: 0.0
  end
end

# iex にて実際に実施するクエリの内容を記載
defmodule MyEcto do
  import Ecto.Query
  alias MyEcto.Weather
  alias MyEcto.Repo

  def sample_query do
    query = from w in Weather,
          where: w.prcp > 0 or is_nil(w.prcp),
         select: w
    Repo.all(query)
  end
end

# 依存関係の解決を行います。

$ mix deps.get
$ mix deps.compile

# マイグレーションファイルを作成する
マイグレーションファイルである create_tables を作成し、作成されたファイルがあることを確認します。

$ mix ecto.gen.migration create_tables
Compiled lib/my_ecto.ex
Generated my_ecto app
* creating priv/repo/migrations
* creating priv/repo/migrations/20150811103823_create_tables.exs
$ ls  priv/repo/migrations/

ファイルがあるだけではマイグレーションは実行できないため
マイグレーションする内容を記載します。
今回の記載内容はEctoのサンプルと同じものとなります。

$ vim priv/repo/migrations/YYYYXXXXXXXXXX_create_tables.exs

YYYYXXXXXXXXXX_create_tables.exsを下記へと書き換えます。

defmodule Repo.CreateTables do
  use Ecto.Migration

  def up do
    create table(:weather) do
      add :city_id, :integer
      add :temp_lo, :integer
      add :temp_hi, :integer
      add :prcp,    :float
      timestamps
    end
    create index(:weather, [:city_id])

    create table(:cities) do
      add :name, :string, size: 40, null: false
      add :country_id, :integer
    end
    create index(:cities, [:country_id])

    create table(:countries) do
      add :name, :string, size: 40, null: false
    end
  end

  def down do
    drop index(:weather, [:city_id])
    drop index(:cities,  [:country_id])
    drop table(:weather)
    drop table(:cities)
    drop table(:countries)
  end
end

# Ectoを実際に使ってみてる
Ectoを実際に使ってみましょう。
まずはマイグレッションを実行します。

$ mix ecto.create
The database for MyEcto.Repo has been created.
$ mix ecto.migrate
19:52:04.911 [info]  == Running MyEcto.Repo.Migrations.CreateTables.up/0 forward
19:52:04.912 [info]  create table weather
19:52:04.952 [info]  create index weather_city_id_index
19:52:04.956 [info]  create table cities
19:52:04.969 [info]  create index cities_country_id_index
19:52:04.973 [info]  create table countries
19:52:04.991 [info]  == Migrated in 0.7s

lib/my_ecto.exに記載したsample_queryを実施する

$ iex -S mix
iex(1)> MyEcto.sample_query
19:53:44.440 [debug] SELECT w0."id", w0."city_id", w0."temp_lo", w0."temp_hi", w0."prcp" FROM "weather" AS w0 WHERE ((w0."prcp" > 0.0::float) OR w0."prcp" IS NULL) [] OK query=417.9ms queue=20.0ms
[]
iex(2)> 

iex -S mix にて MyEcto.sample_query を実施し、Selectをpostgresqlに投げることができました。
特に今回はテーブル上に何も入れていないため結果は返ってきておりません。

# まとめ
今回はElixirにてデータベースとアクセスし、データを保存するためのライブラリであるEctoを紹介させていただきました。
Ecto単体では使いどころが難しいかもしれませんが、Elixirで一番有名である?WebフレームワークのPhoenixでも使用されており、データベースにアクセスするライブラリとしては将来性も高いため内側まで知っていても損はないと思います。

それでは皆さま、良いElixirライフを