スポンサーリンク

2016年8月11日

[Elixir]夏コミ用の内容を一部公開(第二弾)

Goal

  • 2016年度の夏コミ用の内容を一部公開

Dev-Environment

OS: Windows8.1
Erlang/OTP: v19.0 (EShell v8)
Elixir: v1.3.0
Phoenix: v1.2

Context

夏コミ用に執筆している本の一部を先行して公開します。
公開している内容は、Ectoの外部キーについてです。
問題があったら消すかもしれません。購入の参考になったら幸いです。

外部キーの作成

マイグレーションから外部キーを作成してみましょう。
外部キーを作るには、Ecto.Migration.references/2を使います。
新しく:tasksテーブルを追加し、:usersテーブルへの外部キーを作成します。
マイグレーションファイルの内容は下記のようになります。

Example:

defmodule Ecto2Example.Repo.Migrations.CreateTask do
  use Ecto.Migration

  def change do
    create table(:tasks) do
      add :name, :string
      add :user_id, references(:users, on_delete: :nothing)

      timestamps()
    end
    create index(:tasks, [:user_id])

  end
end
外部キーを指定するには、referencesを利用します。
簡単にですが、オプションについて説明をしたいと思います。
  • :name
外部キー名の指定ができます。
外部キー名は、デフォルトだと”[table]_[column]_fkey”と言う名前になります。
今回の場合ですと、”posts_user_id_fkey”ですね。
  • :column
外部キー列の指定ができます。デフォルトでは:idになっています。
上記の例だと:usersの:idになりますね。これを:usersの:nameに変えたいとなったときに指定するオプションです。
  • :type
外部キーのタイプを指定することができます。デフォルトでは、:serialになっています。
  • :on_delete
エントリが削除された場合、どうするかの設定が行えます。
設定には、何もしない(:nothing)、全て削除(:delete_all)、全て無効(:nilify_all)があります。
デフォルトでは、:nothingになっています。
  • :on_update
:on_deleteオプションとほぼ同じです。
エントリが更新された場合、どうするかの設定が行えます。
設定には、何もしない(:nothing)、全て更新(:update_all)、全て無効(:nilify_all)があります。
デフォルトでは、:nothingになっています。

Note:

:on_delete、:on_updateオプションについてもう少し詳しく・・・

削除か更新かでしかほぼ変わらんので、:on_deleteを例に取って説明します。
例えば、:usersテーブルと:tasksテーブルが1対多の関係に設定するとします。
:tasksのマイグレーションファイルの外部キーで:on_deleteオプションを:delete_allに設定していると、
:usersの行データが消されたとき、:tasksの関係しているデータも消してくれるということです。

別段、トランザクションの一連の流れとして先に削除してもいいですし、
自分で:tasksのデータを消した後、:usersのデータを削除してもいいです。
まぁ、これを設定していると手間が省けるということですね。

ただ、プログラム上ではRepo.delete/2でユーザのデータを消しているだけに見えますから、
情報を共有していないと関連するデータの削除を別の人がプログラム上で行ってしまうことがあるかもしれません。

Example:

> ecto.migrate
データベースへ接続し、:users、:tasksテーブルを確認してみます。

Example:

                                      Table "public.users"
   Column    |            Type             |                     Modifiers
-------------+-----------------------------+----------------------------------------------------
 id          | integer                     | not null default nextval('users_id_seq'::regclass)
 name        | character varying(255)      |
 email       | character varying(255)      |
 inserted_at | timestamp without time zone | not null
 updated_at  | timestamp without time zone | not null
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)
Referenced by:
    TABLE "tasks" CONSTRAINT "tasks_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)
:tasksへの参照が追加されています。

Example:

                                      Table "public.tasks"
   Column    |            Type             |                     Modifiers
-------------+-----------------------------+----------------------------------------------------
 id          | integer                     | not null default nextval('tasks_id_seq'::regclass)
 name        | character varying(255)      |
 user_id     | integer                     |
 inserted_at | timestamp without time zone | not null
 updated_at  | timestamp without time zone | not null
Indexes:
    "tasks_pkey" PRIMARY KEY, btree (id)
    "tasks_user_id_index" btree (user_id)
Foreign-key constraints:
    "tasks_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)
インデックスと外部キーが設定されていますね。

Note:

Phoenixの生成コマンドからモデルまで含めて一括で外部キーを作ることができます。

> mix phoenix.gen.model Task tasks name:string user_id:references:users

上記のコマンドで生成されるモデルファイルは下記のものになります。
また、生成されるマイグレーションファイルは上記の方で掲載しているものと同じになります。

defmodule Ecto2Example.Task do
  use Ecto2Example.Web, :model

  schema "tasks" do
    field :name, :string
    belongs_to :user, Ecto2Example.User

    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:name])
    |> validate_required([:name])
  end
end

belongs_toが追加されています。便利ですね。
たとえ一行であっても自分で書くの減らしたいと言う方のためにあるのでしょう。(嘘です)

Bibliography

なし

2016年8月10日

[Elixir]ページネーション(Scrivener)の検証

Goal

  • 最新(2016/8/10)のScrivenerパッケージの使い方を試す

Dev-Environment

開発環境は下記のとおりです。
  • OS: Windows8.1
  • Erlang: Eshell V8.0, OTP-Version 19.0
  • Elixir: v1.3.0
    • Phoenix Framework: v1.2.0
    • Scrivener: v2.0.0
    • Scrivener_html: v1.3.1
    • Scrivener_ecto: v1.0

Context

以前に利用をさせていただいてから大分時間が経過していますので、
ページネーション用のパッケージであるGithub - drewolson/scrivenerがどれくらい変わったのか試してみます。
プロジェクトの作成などは特にしません。(ソース中は”TutorialVerification”となっていますので、適宜変更してください)
まずは、パッケージを追加します。

File: mix.exs

defmodule TutorialVerification.Mixfile do
  ...

  # Specifies your project dependencies.
  #
  # Type `mix help deps` for examples and options.
  defp deps do
    [...
     {:scrivener, "~> 2.0"},
     {:scrivener_ecto, "~> 1.0"},
     {:scrivener_html, "~> 1.3.1"}]
  end

  ...
end
次にconfig.exsへ設定を追加します。

File: config/config.exs

use Mix.Config

...

config :scrivener_html,
  routes_helper: TutorialVerification.Router.Helpers

import_config "#{Mix.env}.exs"
repo.exでScrivenerをuseします。
:page_sizeはデフォルトのページサイズです。

File: lib/[my_app_name]/repo.ex

defmodule TutorialVerification.Repo do
  use Ecto.Repo, otp_app: :tutorial_verification
  use Scrivener, page_size: 10
end
表示用に適当なモデル、コントローラ、ビューを追加します。
面倒なので、Phoenixのコマンドを利用してしまいましょう。

Example:

> mix phoenix.gen.html User users name:string email:string
...
作成されたビューへScrivener.HTMLのimportを追加します。

File: web/views/user_view.ex

defmodule TutorialVerification.UserView do
  use TutorialVerification.Web, :view
  import Scrivener.HTML
end
適当な表示用データが欲しいので、seeds.exsで投入してしまいましょう。
下記のようにすれば100個のデータが入ります。

File: priv/repo/seeds.exs

alias TutorialVerification.{Repo, User}

Enum.each(1..100, fn(number) ->
                    User.changeset(%User{}, %{name: "test#{number}", email: "test#{number}@test.com"})
                    |> Repo.insert!
                  end)
コントローラのindexアクションを下記のように変更します。

File: web/controllers/user_controller.ex

defmodule TutorialVerification.UserController do
  ...

  def index(conn, params) do
    users = User
            |> order_by(desc: :name)
            |> Repo.paginate(params)

    render(conn, "index.html", users: users)
  end

  ...
end

Note: Repo.paginate/2のコンフィグで直接、ページとページサイズを指定することができます。

下記のようにすると、(:page)2ページ目の表示指定と(:page_size)DBか20個のデータを取得するようになります。

defmodule TutorialVerification.UserController do
  ...

  def index(conn, params) do
    users = User
            |> order_by(desc: :name)
            |> Repo.paginate(page: 2, page_size: 20)

    render(conn, "index.html", users: users)
  end

  ...
end
テンプレートへページネーションのリンクを作成するようにします。
下記のようになります。

File: web/templates/user/index.html.eex

<h2>Listing users</h2>

<div>
  <%= pagination_links @conn, @users %>
</div>

<table class="table">
  ...
</table>

<div>
  <%= pagination_links @conn, @users %>
</div>

<%= link "New user", to: user_path(@conn, :new) %>
実行して表示を見てみましょう。
ページのparamsで指定していればページ移動もできています。
変わったと感じたのは、やはりテンプレートの部分ですね。
scrivener_ectoの部分は基本的に変わっていないと思います。
scrivener_htmlでページリンクが作れるようになったのは便利ですね。
前に使ったときは、ページリンクのテンプレート部分を自分で作っていたので…。
これなら、Phoenixチュートリアルのソースも減りますね。
(…厚い本が薄くなるかも???(冗談))
個人的に思ったのは、パッケージがそれぞれ別々になっているのはなんでなんでしょう?
別に一つに統合してもいい気がしますが?まぁ、作成者様に私には思いつかない何か考えがあるのでしょう。
使う上では特に支障もありませんしね。

Bibliography

参考にした書籍及びサイトの一覧は下記になります。

人気の投稿