Edited at

ElixirでSI開発入門 #8 Railsからのモデルの移行1(FitGap分析)

More than 1 year has passed since last update.

(この記事は、「fukuoka.ex(その2) Elixir Advent Calendar 2017」の16日目、および「Data Platform Advent Calendar 2017」の4日目です)

昨日は、@piacere_ex さんの「Elixirでデータ分析入門#2:インプットしたデータを変換する(前処理①)」でした


はじめに

Elixirで実際にプロダクト開発した経験からサンプルコードを交えて解説する本連載

今回から数回に渡り別言語のフレームワークで構築されたDBへ対してElixir+Phoenixで接続する際に必要なモデルの移行を考えます。

今回はRuby on RailsでMySQLに作成したテーブルを例に、

既存システムのDB情報を元にEctoモデルを自動作成するツールを開発します。

Railsの実行速度に限界を感じている方にはぜひ読んでいただきたい内容です。

本連載の記事はこちら

|> ElixirでSI開発入門 #1 Ectoで悲観的ロック

|> ElixirでSI開発入門 #2 Ectoで楽観的ロック

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

|> ElixirでSI開発入門 #4 本番パスワードを環境変数に持たせる

|> ElixirでSI開発入門 #5 Ectoで自由にSQLを書いて実行する(参照編)

|> ElixirでSI開発入門 #6 Ectoで自由にSQLを書いて実行する(更新編)

|> ElixirでSI開発入門 #7 Multiで使う関数を再利用可能な粒度に分割する

|> ElixirでSI開発入門 #8 Railsからのモデルの移行1(FitGap分析)

 ★★★お礼:各種ランキングに83回のランクインを達成しました ★★★

4/27から、37日間に渡り、毎日お届けしている「季節外れのfukuoka.ex Elixir Advent Calendar」「季節外れのfukuoka.ex(その2) Elixir Advent Calender」ですが、Qiitaトップページトレンドランキングに12回入賞、Elixirウィークリーランキングでは6週連続で1/2/3フィニッシュを飾り、各種ランキング通算で、トータル83回ものランクインを達成しています

みなさまの暖かい応援に励まされ、合計552件ものQiita「いいね」もいただき、fukuoka.exアドバイザーズとfukuoka.exキャストの一同、ますます季節外れのfukuoka.ex Advent Calendar、頑張っていきます!

image.png


想定される課題


課題1 Rails(ActiveRecord)とEctoの型のギャップ

 データ移行や、システムリプレイス、外部IF通信など、

 異なる言語、フレームワーク、ミドルウェア間でデータのやり取りを行う場合、

 必ずぶち当たるのが型桁のギャップの問題です。

 ElixirはRubyの影響を強く受けているものの、

 双方の代用的ORマッパーであるActiveRecordとEctoにもギャップが存在する可能性が高く、

 双方で相いれないデータ定義をどう処理するかが課題となります。


課題2 DDLから取得できない情報の存在

 何らかの成果物(今回はDDL)を解析する場合、各実装におとす過程で消失し、不可逆な情報が存在します。

 今回のケースでいうと、MIXタスクでモデルを生成する時に必要な情報のうち、コンテキスト名は変換しても求めることができません。

項目
取得可否
記法
備考

コンテキスト名
不可
UpperCamelCase
概念の問題なので自動的に変換できない

モジュール名

UpperCamelCase
複数形から単数形への変換が必要

複数形名

sneak_case
ActiveRecordでMigrationすればテーブル名は複数形


実装の前提

最終的には


  • Railsで実装されたモデルに対応するDBのDDLからEctoモデルを作成する

を目標にしますが、

その前段として、課題1のRailsとEctoの型に関するFitGap分析を行います。


  • Railsで代表的な型を定義したモデルを生成する。

  • Ectoで同じく代表的な型を定義したモデルを作成する。

  • 双方のマイグレーション結果のDDLから型のFitGapを整理する

  • DBはMySQLを使用する

また、以下の環境で実装しました


  • Elixir v1.6.1

  • Phoenix v1.3.2

  • Ecto v2.2.10

  • Ruby v2.2.3p173

  • Rails v5.2.0

  • MySQL v5.7.22


事前準備


Rails環境の構築

まず、事前準備としてRuby on Railsの開発環境を構築します。

下記の記事を参考に実行しました。

最速!MacでRuby on Rails環境構築


MySQLのインストール

また、今回はDBとしてMySQLを対象とする為、こちらも下記記事を参考にインストールしローカル端末上で起動しました。

Mac での MySQL あれこれ


DBの作成

mysqlコマンドでDBを作成します。

> mysql -u root

mysql CREATE USER admin IDENTIFIED BY 'password' ;
mysql> CREATE DATABASE mig_sample_development ;
mysql> use mig_sample_production ;


Rails側のモデルの作成

任意のフォルダで以下のコマンドを実行し、Railsプロジェクトを作成します。

> bundle exec rails new mig_sample -d mysql

> cd mig_sample

作成したプロジェクトフォルダにて以下のコマンドを実行し、モデルを作成します。

※モデルはデータ型の確認のため、一般的によく使う型を一通り定義した業務的には特に意味を持たないモデルです。

> rails g model Sample col_stg:string:index col_text:text col_float:float col_integer:integer:unique col_decimal:decimal col_datatime:datetime col_time:time col_date:date col_binary:binary col_boolean:boolean

モデルと同時に以下のマイグレーションファイルが作成されるので確認する。


db/migrate/20180516023237_create_samples.rb

class CreateSamples < ActiveRecord::Migration[5.2]

def change
create_table :samples do |t|
t.string :col_stg
t.text :col_text
t.float :col_float
t.integer :col_integer
t.decimal :col_decimal
t.datetime :col_datatime
t.time :col_time
t.date :col_date
t.binary :col_binary
t.boolean :col_boolean

t.timestamps
end
add_index :samples, :col_stg
end
end


rakeタスクでマイグレーションを実行する。

 (Elixirでのmix ecto.migrateに相当)

>rake db:migrate

マイグレーションを実行した結果作成されたテーブルのDDLが以下となる。

CREATE TABLE `samples` (

`id` bigint(20) NOT NULL AUTO_INCREMENT,
`col_stg` varchar(255) DEFAULT NULL,
`col_text` text,
`col_float` float DEFAULT NULL,
`col_integer` int(11) DEFAULT NULL,
`col_decimal` decimal(10,0) DEFAULT NULL,
`col_datatime` datetime DEFAULT NULL,
`col_time` time DEFAULT NULL,
`col_date` date DEFAULT NULL,
`col_binary` blob,
`col_boolean` tinyint(1) DEFAULT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `index_samples_on_col_stg` (`col_stg`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


検証用にElixirのモデルも作成

移行プログラムを作成するにあたって、検証用にElixirでも同じようなモデルを作成する。

> mix phx.new ecto_mig_sample --database mysql

> cd ecto_mig_sample/
> mix ecto.create
> mix phx.gen.html Samples Sample samples col_string:string col_int:integer:uniquecol_float:float col_boolean:boolean col_binary:binary col_decimal:decimal col_map:map col_date:date col_time:time col_u_datetime:utc_datetime
mix ecto.migrate

Elixirのマイグレーションで作成されたテーブルのDDLがこちらである。

CREATE TABLE `samples` (

`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`col_string` varchar(255) DEFAULT NULL,
`col_int` int(11) DEFAULT NULL,
`col_float` double DEFAULT NULL,
`col_boolean` tinyint(1) NOT NULL DEFAULT '0',
`col_binary` blob,
`col_decimal` decimal(10,0) DEFAULT NULL,
`col_map` text,
`col_date` date DEFAULT NULL,
`col_time` time DEFAULT NULL,
`col_u_datetime` datetime DEFAULT NULL,
`inserted_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `samples_col_int_index` (`col_int`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


型のGapを確認する

上記の検証結果から今回対象とする

Railsのデータ型 -> MySQLのデータ型 -> Ectoのデータ型

の対応を整理したのが以下の表である。

Rails
MySQL
Ecto
Elixir

id
bigint(20)
id
integer

string
varchar(255)
:string
UTF-8 encoded string

text
text
※1
-

N/A
N/A
:map
map

float
float
※2
-

float #limitの範囲が24~53
double
:float
float

integer #limitが1
tinyint(1)
※3
-

boolean
tinyint(1)
:boolean
boolean

integer
int(11)
:integer
integer

decimal
decimal(10,0)
:decimal
Decimal

datetime
datetime
:utc_datetime
DateTime

time
time
:time Time
Time

date
date
:date Date
Date

binary
blob
:binary
binary

※1 Ectoの型に現状TEXTはないただし、先ほどの検証結果からmap型を指定するとDB上はTEXTに対応するが意味合いが異なるため、今回は考慮の対象外とする。

※2 Ectoにはfloat型の定義があるが上記の検証結果から特に桁数を指定しない場合のMySQLのデータ型としては doubleに相当する。

 今回はRailsからの移行が目的のため、桁数定義による厳密な型の対応は割愛する。

※3 本来tinyint(1)はデータ型としてtynyint(1)でしかないのだが、MySQLの仕様上Railsおよび、Ectoでのbooleanがtinyint(1)としてMySQL上で扱われる。

   ややこしいので、今回のツールではtinyintは対象外とし、tinyint(1)=booleanとして扱う


まとめ

DDLからカラム定義と型を判別し、ecto.schemaの対応する型としてMixでgenしてやればモデルの以降を半自動化できそうです。

(コンテキストの問題があるので全自動化はできない)

次回は、実際のツールの開発に入ります。

お楽しみに〜〜 (^_^)/

明日は、@twinbee さんの「Elixirから簡単にRustを呼び出せるRustler#5 NIFからメッセージ送信を行う」です

★★★ 満員御礼!Elixir MeetUpを6月末に開催します ★★★

※応募多数により、増枠しました

「fukuoka.ex#11:DB/データサイエンスにコネクトするElixir」を6/22(金)19時に開催します

私もこの連載で取り上げてない、開発ネタを出す予定ですのでぜひ奮ってご応募ください。

image.png