Phoenixでのシードデータ投入
phoenixでは、
priv/repo/seeds.exs
%App.SomeModel{}
|> App.Repo.insert!
的なelixirコードを記述して mix run priv/repos/seed.exs
と実行してやれば、シードデータ投入が出来るみたいです。
mix ecto.reset
を実行すると、drop→create→migrate→seed投入、の順番で実行します。開発の初期段階だと便利ですね。
しかし、まぁ投入データをEctoの記法でいちいち記述するのも面倒臭いので、YAMLで書けた方が便利(?)と思ったのでまー試しにやって見ます。
必要なモジュール
mix.exs
{:yaml_elixir, "~> 1.1"},
$ mix deps.get
データベース設定
MySQL 使ってます。
config/dev.exs
config :app, App.Repo,
adapter: Ecto.Adapters.MySQL,
username: "root",
password: "",
database: "app_dev",
hostname: "mysql",
pool_size: 10
モデル
AreaモデルとPrefectureモデルを想定します。
Area : Prefecture = 1 : n の関係にあります。
Prefectureが、Areaのcodeをarea_codeとして持っている形です。
web/models/area.ex
defmodule App.Area do
use Ecto.Schema
alias App.Prefecture
@primary_key {:code, :string, autogenerate: false}
schema "areas" do
field :name, :string
has_many :prefectures, Prefecture,
references: :code,
foreign_key: :area_code
end
end
web/models/prefecture.ex
defmodule App.Prefecture do
use Ecto.Schema
alias App.Area
@primary_key {:code, :string, autogenerate: false}
schema "prefectures" do
field :name, :string
belongs_to :area, Area,
references: :code,
foreign_key: :area_code,
type: :string
end
end
Fixtureモジュール定義
yamlを読み込んでデータ投入するためのモジュールを作成します。
別のプロジェクトでも使えるようにする為に、App.Repoの関数呼んでるところをもうちょっと抽象化したい感じがします。
web/models/concerns/fixture.ex
defmodule App.Model.Fixture do
alias App.Model.Fixture
def create(model) do
struct(model)
end
def create(model, params) do
struct(model, params)
end
def primary_key(model, params) do
create(model, params)
|> Ecto.Changeset.cast(params, Map.keys( params ))
|> Ecto.Changeset.apply_changes
|> Ecto.primary_key
end
def find(model, primary_key) do
App.Repo.get_by model, primary_key
end
def insert(file_name, model) do
Fixture.get_params_list_from(file_name)
|> Fixture.insert_list(model)
end
def insert_or_update(file_name, model) do
Fixture.get_params_list_from(file_name)
|> Fixture.insert_or_update_list(model)
end
def get_params_list_from(file_path) do
File.cwd!
|> Path.join(file_path)
|> YamlElixir.read_from_file
end
def insert_list(params_list, model) do
for params <- params_list do
model.change(params)
|> App.Repo.insert!
end
end
def insert_or_update_list(params_list, model) do
for params <- params_list do
object = find(model, primary_key(model, params) ) || create(model)
object
|> Ecto.Changeset.cast(params, Map.keys( params ))
|> App.Repo.insert_or_update!
end
end
end
定義ファイル
priv/repo/fixtures/areas.yml
- code: '01'
name: 北海道・東北
- code: '02'
name: 関東
- code: '03'
name: 甲信越・北陸
- code: '04'
name: 東海
- code: '05'
name: 関西
- code: '06'
name: 中国
- code: '07'
name: 四国
- code: '08'
name: 九州・沖縄
priv/repo/fixtures/prefectures.yml
- code: '01'
name: '北海道'
area_code: '01'
- code: '02'
name: '青森県'
area_code: '01'
- code: '03'
name: '岩手県'
area_code: '01'
- code: '04'
name: '宮城県'
area_code: '01'
- code: '05'
name: '秋田県'
area_code: '01'
- code: '06'
name: '山形県'
area_code: '01'
- code: '07'
name: '福島県'
area_code: '01'
- code: '08'
name: '茨城県'
area_code: '02'
- code: '09'
name: '栃木県'
area_code: '02'
- code: '10'
name: '群馬県'
area_code: '02'
- code: '11'
name: '埼玉県'
area_code: '02'
- code: '12'
name: '千葉県'
area_code: '02'
- code: '13'
name: '東京都'
area_code: '02'
- code: '14'
name: '神奈川県'
area_code: '02'
- ...
- ...
-
実行ファイル
priv/repo/seeds.exs
alias App.Model.Fixture
Fixture.insert_or_update("priv/repo/fixtures/areas.yml", App.Area)
Fixture.insert_or_update("priv/repo/fixtures/prefectures.yml", App.Prefecture)
いちいち指定するのが面倒であれば、priv/repo/fixtures以下のymlを全部見るようにしても良さそう。
Fixtureの関数として追加してもいい気がする。
実行
$ mix run priv/repo/seeds.exs
Ruby/Railsは4〜5年使っていますが、Elixir/Phoenixは始めて10日程度の素人です。
もうちょっといい方法や書き方とかがあったら教えていただけると幸いです。