LoginSignup
13
6

More than 5 years have passed since last update.

ElixirでSI開発入門 #3 主キーが"id "じゃない既存DBへの接続

Last updated at Posted at 2018-05-08

(この記事は、「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への接続

:stars::stars::stars::stars::stars: お知らせ :stars::stars::stars::stars::stars:
「fukuoka.ex#11:DB/データサイエンスにコネクトするElixir」を6/22(金)19時に開催します
私も本連載で出してないSI開発ネタを出す予定ですので、
Elixirでプロダクト開発最前線に興味ある方はぜひぜひご応募ください!

image.png

カスタム・プライマリキー設定で主キーを変更

こちらも公式にサポートされており、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

ルーティングを追加

lib/ecto_custome_primary_key_sample_web/router.ex

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の項目はない

lib/ecto_custome_primary_key_sample/items/item.ex

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
priv/repo/migrations/yyyyMMddhhmmss_create_items.exs
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
) ;

カスタム・プライマリキーの設定

スキーマに主キー項目を指定する記述を追加する

lib/ecto_custome_primary_key_sample/items/item.ex
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

マイグレーションを行う場合はマイグレーションファイルにも主キー設定を追加する

priv/repo/migrations/yyyyMMddhhmmss_create_items.exs
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を登録してみる。

http://localhost:4000/items

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段ステージ構造を理解する」です。

13
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
6