(この記事は、「Elixir or Phoenix Advent Calendar 2017」の12日目です)
昨日は、@piacere さんの「ExcelでElixirマスター3回目:WebにDBデータ表示でした
はじめに
Elixirで実際にプロダクト開発した経験からサンプルコードを交えて解説する本連載
前回までに引き続きDB関連のお話です。
Elixirで実装された軽量ORマッパーであるEctoはデフォルトでidというサロゲートキーを主キーとするスキーマ実装とマイグレーションファイルを生成します。
ORマッパーが一般的でなかった時代からDB設計をやっていたおじさんからすると、そもそも"id"という味もそっけもない主キーが気持ち悪くもあるのですが、モダンなフレームワークの潮流なのでそこは慣れるとして・・・
既存のシステムで作成されたデータベースに繋ぎたい場合ってありますよね?
- 創り込んじゃった基幹系を全改修するお金はないけど、追加開発する情報系はサーバーコストもかけたくないからElixirで・・・とか
- IOTからデータ流れるサーバーが既存言語で耐えられなくなってるのでElixirで・・・とか
- あとは自分が単にElixirやってみたいから!とか(これエンジニアにとっては大事なモチベーションですよね)
でも既存DBは主キーが item_code:stringとかだったりします。
今回はそんな id:integer じゃない主キーを設定する方法についてです。
本連載の記事はこちら
|> ElixirでSI開発入門 #1 Ectoで悲観的ロック
|> ElixirでSI開発入門 #2 Ectoで楽観的ロック
|> ElixirでSI開発入門 #3 主キーが"id "じゃない既存DBへの接続
お知らせ
「fukuoka.ex#11:DB/データサイエンスにコネクトするElixir」を6/22(金)19時に開催します
私も本連載で出してないSI開発ネタを出す予定ですので、
Elixirでプロダクト開発最前線に興味ある方はぜひぜひご応募ください!
カスタム・プライマリキー設定で主キーを変更
こちらも公式にサポートされており、phoenixframework.orgにわかりやすい記事があがっています。
実装の前提
今回は以下の実装を例に考えます
- item_code:stringを主キーにもつitemsテーブルに接続する
- 今回はサンプル実装のためにマイグレーションファイルも生成する
また、以下の環境で実装しました
- Elixir v1.6.1
- Phoenix v1.3.2
- Ecto v2.2.10
- PostgreSQL v10.2
開発手順
プロジェクト〜モデルの作成
PhoenixプロジェクトとDBを作成
> mix phx.new ecto_custome_primary_key_sample --no-brunch
> cd ecto_custome_primary_key_sample
> mix ecto.create
モデルを作成
> mix phx.gen.html Posts Comment comments name comment lock_version:integer
※既存DBに接続する場合は、マイグレーションは不要なので**--no-schema**オプションを指定すると不要なマイグレーションファイルが作成されない
> mix phx.gen.html Posts Comment comments name comment lock_version:integer --no-schema
ルーティングを追加
defmodule EctoCustomePrimaryKeySampleWeb.Router do
use EctoCustomePrimaryKeySampleWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", EctoCustomePrimaryKeySampleWeb do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
resources "/items", ItemController # <-- 作成したモデルのルーティングを追加
end
end
デフォルトの実装
一旦マイグレーションを実行して、出来上がった実装を確認してみる
> mix ecto.migrate
スキーマにもマイグレーションファイルにもidの項目はない
defmodule EctoCustomePrimaryKeySample.Items.Item do
use Ecto.Schema
import Ecto.Changeset
schema "items" do
field :category, :string
field :item_code, :string
field :name, :string
timestamps()
end
〜中略〜
end
defmodule EctoCustomePrimaryKeySample.Repo.Migrations.CreateItems do
use Ecto.Migration
def change do
create table(:items) do
add :item_code, :string
add :category, :string
add :name, :string
timestamps()
end
end
end
が、出来上がったテーブルには"id"が主キーとして追加されている。
主キーがid:integerであることが暗黙的に実装されているのだ
CREATE TABLE public.items (
id bigserial NOT NULL,
item_code varchar(255) NULL,
category varchar(255) NULL,
name varchar(255) NULL,
inserted_at timestamp NOT NULL,
updated_at timestamp NOT NULL,
CONSTRAINT items_pkey PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
) ;
カスタム・プライマリキーの設定
スキーマに主キー項目を指定する記述を追加する
defmodule EctoCustomePrimaryKeySample.Items.Item do
use Ecto.Schema
import Ecto.Changeset
@primary_key {:item_code, :string, []} # <-- 主キー項目と型を指定する
@derive {Phoenix.Param, key: :item_code} # <-- Phoenixがパラメータキーとして何を使用して変換するかを指定する
schema "items" do
field :category, :string
# field :item_code, :string <-- 重複定義となるためfield指定は削除するかコメントアウト
field :name, :string
timestamps()
end
@doc false
def changeset(item, attrs) do
item
|> cast(attrs, [:item_code, :category, :name])
|> validate_required([:item_code, :category, :name])
end
end
マイグレーションを行う場合はマイグレーションファイルにも主キー設定を追加する
defmodule EctoCustomePrimaryKeySample.Repo.Migrations.CreateItems do
use Ecto.Migration
def change do
create table(:items, primary_key: false) do # <-- ", primary_key: false"を追加
add :item_code, :string, primary_key: true # <-- ", primary_key: true"を追加
add :category, :string
add :name, :string
timestamps()
end
end
end
テーブルを再作成する。
一旦DBに接続してるセッションを全て切断し(DBクライアントツール、phoenix、iexなど)ecto.resetを実行するとDB再作成&シードデータの登録(priv/repo/seeds.exs)が再実行される。
> mix ecto.reset
改めて確認すると、item_code:string が主キーのテーブルが作成されている。
CREATE TABLE public.items (
item_code varchar(255) NOT NULL,
category varchar(255) NULL,
name varchar(255) NULL,
inserted_at timestamp NOT NULL,
updated_at timestamp NOT NULL,
CONSTRAINT items_pkey PRIMARY KEY (item_code)
)
WITH (
OIDS=FALSE
) ;
動かしてみる
Phoenixサーバーを起動する。
> mix ecto.migrate
> mix phx.server
以下のURLにアクセスし、適当にItemを登録してみる。
URLも主キー文字列でアクセスできる
http://localhost:4000/items/X-01
ちなみに、文字列型であれば2バイト文字の主キーも登録可能
http://localhost:4000/items/%E4%BC%8A%E3%81%AE6%E5%8F%B7
まあ、SIでは基本避けますが・・・
まとめ
- idが主キーでない時にはCustom Primary Keys
- @primary_key、 @derive で主キーを指定
- --no-schemaオプションでマイグレーション対象外
いかがだったでしょうか。
これで既存システムのElixirリプレイスもバッチリですね!
明日は、@twinbee さんの「Elixir並列処理「Flow」の2段ステージ構造を理解する」です。