Ruby
Rails

RailsのDBの初期データ(rake db:seed用)をyamlで美しく管理する方法


この記事のサマリ

RailsにおいてDBの初期データは、seed.rbを記述することで設定することが可能ですが、全てコーディングしてしまうと見辛いので、yamlで管理する方法を紹介します。

管理するyamlファイルは、開発していく中で動かしながら検討した動作実績のあるデータを、そのままファイルに出力して活用する方式としています。

yaml_dbなどの、Gemは特に利用していません。(yaml_dbだと、rake db:seed以外のrakeコマンドが必要になりそうであり、標準のrake db:seedを利用するようにしておきたかったため)


動作確認バージョン

Rails 4.1.1

ruby 2.1.2p95


経緯

railsのアプリのDBの初期データって、seed.rbに記述してrake db:seedってのがセオリーだと認識しているのですが、seed.rbにコーディングすると、イマイチ初期データの視認性が悪いので、なにか視認性の高いフォーマットで管理したい...

視認性の高いフォーマットという視点で考えたときに、csvやtsvよりは、xmlとかjsonとかyamlの方が、分かりやすい...

Rubyでやるならば、とりあえずRubyと親和性の高いyamlで管理するようにしようと思ったのが発端です。(yaml_dbで管理するのも在りですが、標準で用意されているrake db:seedではない、rakeタスクの実行が必要になりそう(seed.rbにyaml_dbのrakeタスクの呼び出せるならそれで代替するのはありかも?)だったので、使わない方針としています。

また、seed.rbでセットアップしたいようなマスタデータの中身って、実際には開発しながら固めていくケースも多いと思います。なので、「rails console」で、楽にyamlに出力できるようにしておきたいと考えました。


具体的な手順

とりあえずサンプルとして、railsアプリを作成する。

rails new initDbDataSample

cd initDbDataSample/

# 初期データ用のyamlを保存するフォルダを作成
mkdir db/seeds

# サンプル用に、「状態」を管理するマスターDBのscaffoldを作成
rails g scaffold db_status status_name:text
rake db:migrate

# なんか適当にデータを投入する。
rails c

irb(main):001:0> ['hoge','fuga','piyo'].each { |status| DbStatus.create status_name:status }
(0.3ms) begin transaction
SQL (1.1ms) INSERT INTO "db_statuses" ("created_at", "status_name", "updated_at") VALUES (?, ?, ?) [["created_at", "2016-02-06 16:17:12.384006"], ["status_name", "hoge"], ["updated_at", "2016-02-06 16:17:12.384006"]]
(5.2ms) commit transaction
(0.2ms) begin transaction
SQL (0.7ms) INSERT INTO "db_statuses" ("created_at", "status_name", "updated_at") VALUES (?, ?, ?) [["created_at", "2016-02-06 16:17:12.408840"], ["status_name", "fuga"], ["updated_at", "2016-02-06 16:17:12.408840"]]
(7.0ms) commit transaction
(3.2ms) begin transaction
SQL (0.9ms) INSERT INTO "db_statuses" ("created_at", "status_name", "updated_at") VALUES (?, ?, ?) [["created_at", "2016-02-06 16:17:12.428236"], ["status_name", "piyo"], ["updated_at", "2016-02-06 16:17:12.428236"]]
(10.9ms) commit transaction
[
[0] "hoge",
[1] "fuga",
[2] "piyo"
]
irb(main):002:0> DbStatus.all
DbStatus Load (0.7ms) SELECT "db_statuses".* FROM "db_statuses"
[
[0] #<DbStatus:0x007f81c7835c20> {
:id => 1,
:status_name => "hoge",
:created_at => Sat, 06 Feb 2016 16:17:12 UTC +00:00,
:updated_at => Sat, 06 Feb 2016 16:17:12 UTC +00:00
},
[1] #<DbStatus:0x007f81c78359c8> {
:id => 2,
:status_name => "fuga",
:created_at => Sat, 06 Feb 2016 16:17:12 UTC +00:00,
:updated_at => Sat, 06 Feb 2016 16:17:12 UTC +00:00
},
[2] #<DbStatus:0x007f81c7835770> {
:id => 3,
:status_name => "piyo",
:created_at => Sat, 06 Feb 2016 16:17:12 UTC +00:00,
:updated_at => Sat, 06 Feb 2016 16:17:12 UTC +00:00
}
]
irb(main):003:0> exit

# 上記は、例としてrailsのconsoleから設定したが、開発していく中でデータを整備するのもよし、
# 例えば外部からデータファイルが提供されているなどで、それをloadするのもよし。

登録されているデータをyamlにするコード

# yamlに吐き出す対象のmodelクラス名を指定して、

target_model = DbStatus
# 吐き出したい対象のリストを取得する(下記は全件取得しているが、テスト環境だけのデータが入っているような場合は適宜絞り込む)
target_records = target_model.all
write_filepath = "#{Rails.root}/db/seeds/#{target_model.to_s.tableize}.yml"
open(write_filepath,"w") do |write_file|
write_file.puts target_records.to_yaml
end

上記コードを利用して、例えば以下のような感じで、yamlに出力する。

やっていることとしては、yamlに出力する対象のデータに対応するモデルにて、レコードを抽出したのち、to_yamlでyamlに変換したデータをファイルに書き込むという流れで実施しています。

実際にyamlに吐き出している手順を、以下に例示します。

# 初期化データとして利用したいレコードをファイルに出力する。

rails c

irb(main):001:0> # yamlに吐き出す対象のmodelを指定して、
write_file.puts target_records.to_yaml
endirb(main):002:0* target_model = DbStatus
class DbStatus < ActiveRecord::Base {
:id => :integer,
:status_name => :text,
:created_at => :datetime,
:updated_at => :datetime
}
irb(main):003:0> # 吐き出したい対象のリストを取得する(下記は全件取得しているが、適宜絞り込んでOK)
irb(main):004:0* target_records = target_model.all
DbStatus Load (1.2ms) SELECT "db_statuses".* FROM "db_statuses"
[
[0] #<DbStatus:0x007f81c7e78420> {
:id => 1,
:status_name => "hoge",
:created_at => Sat, 06 Feb 2016 15:39:44 UTC +00:00,
:updated_at => Sat, 06 Feb 2016 15:39:44 UTC +00:00
},
[1] #<DbStatus:0x007f81c7e77d18> {
:id => 2,
:status_name => "fuga",
:created_at => Sat, 06 Feb 2016 15:39:46 UTC +00:00,
:updated_at => Sat, 06 Feb 2016 15:39:46 UTC +00:00
},
[2] #<DbStatus:0x007f81c7e77ac0> {
:id => 3,
:status_name => "piyo",
:created_at => Sat, 06 Feb 2016 15:39:48 UTC +00:00,
:updated_at => Sat, 06 Feb 2016 15:39:48 UTC +00:00
}
]
irb(main):005:0> write_filepath = "#{Rails.root}/db/seeds/#{target_model.to_s.tableize}.yml"
"/home/vagrant/initDbDataSample/db/seeds/db_status.yml"
irb(main):006:0> open(write_filepath,"w") do |write_file|
irb(main):007:1* write_file.puts target_records.to_yaml
irb(main):008:1> end
nil
irb(main):009:0> quit

# ここまでで、下準備はOK
# rake db:seedで初期データを登録するため、データはクリーニングしておく
rake db:migrate:reset

吐き出したyamlの中身は以下のようになっていてますので、人間の目で見ても、非常にわかりやすいです。

cat db/seeds/db_status.yml

---
- !ruby/object:DbStatus
attributes:
id: 1
status_name: hoge
created_at: 2016-02-06 15:39:44.278188000 Z
updated_at: 2016-02-06 15:39:44.278188000 Z
- !ruby/object:DbStatus
attributes:
id: 2
status_name: fuga
created_at: 2016-02-06 15:39:46.503348000 Z
updated_at: 2016-02-06 15:39:46.503348000 Z
- !ruby/object:DbStatus
attributes:
id: 3
status_name: piyo
created_at: 2016-02-06 15:39:48.697909000 Z
updated_at: 2016-02-06 15:39:48.697909000 Z

上記yamlデータをDBに反映するようなseed.rbは以下です。yamlファイル名は、対応するテーブルのモデル名に変換できることを前提としています。


seed.rb

Dir.glob("#{Rails.root}/db/seeds/*.yml").each do |yaml_filename|

# yamlのファイル名から、対応するモデルクラスを特定し、クラスをロードする
# 下記行が無いと、「ArgumentError: undefined class/module」が発生する。
target_model = File.basename(yaml_filename,".yml").classify.constantize
# すでに登録されているデータを全県削除
target_model.delete_all

# yamlに記述されたレコードをDBに登録する。
File.open(yaml_filename) do |load_target_yaml|
records = YAML.load(load_target_yaml)

records.each do |record|
target_model.create(record.attributes)
end
end
end


「rake db:seed」できちんと初期データが反映できるか確認してみます。

# DBがちゃんと初期化できるか確認

rake db:seed

rails c

irb(main):001:0> DbStatus.all
DbStatus Load (4.3ms) SELECT "db_statuses".* FROM "db_statuses"
[
[0] #<DbStatus:0x007f81c6452460> {
:id => 1,
:status_name => "hoge",
:created_at => Sat, 06 Feb 2016 15:59:11 UTC +00:00,
:updated_at => Sat, 06 Feb 2016 15:59:11 UTC +00:00
},
[1] #<DbStatus:0x007f81c6444d60> {
:id => 2,
:status_name => "fuga",
:created_at => Sat, 06 Feb 2016 15:59:11 UTC +00:00,
:updated_at => Sat, 06 Feb 2016 15:59:11 UTC +00:00
},
[2] #<DbStatus:0x007f81c6444a40> {
:id => 3,
:status_name => "piyo",
:created_at => Sat, 06 Feb 2016 15:59:11 UTC +00:00,
:updated_at => Sat, 06 Feb 2016 15:59:11 UTC +00:00
}
]

こんな感じで、できてますね!!

ちょっとしたマスタデータを、gitなどでバージョン管理したい場合に役に立つのではないかなと思います。