Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

この記事のサマリ

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などでバージョン管理したい場合に役に立つのではないかなと思います。

yukimura1227
フルスタック目指して日々、奮闘中。あいつにマネジメントばっかりやらせるなんてもったいないと言われるように、インフラ~アプリ開発~運用ツールまで食わず嫌いせずに精進します!!
http://yukimura1227.blog.fc2.com/
gi-no
IT/Webエンジニアに特化した転職・学習サービス "paiza (パイザ)" を開発・運営しています。【異能をのばせ】をミッションとして、IT人材のスキル/経験を可視化する成長プラットフォームを目指しています。
paiza.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした