seed-fu を使うのが、初期データの入れ方としては良いみたいです。
railsにある仕組みとして rake db:seed
があります。
これは、何も考えずに使うと実行するたびに同じデータが登録されてしまいます。
そこで、代わりにseed-fuを利用するわけです。
seed-fuは、すでに存在しているが変更したいレコードだけ更新したり、ファイル単位で実行できたり、簡単に書けるようなシンタックスシュガーがあったりと便利です。
インストール
Gemfileに記述してインストールします。
(Rails 3.1, 3.2, 4.0, 4.1の場合は2.3を使う)
gem 'seed-fu', '~> 2.3'
$ bundle install
rake task
rakeタスクを実行する前に、seedファイルを置くディレクトリを作成します。
$ mkdir db/fixtures
$ mkdir db/fixtures/development
$ mkdir db/fixtures/production
db/fixtures
は最低限必要なディレクトリで、この下にseedファイルを置きます。
環境ごとに作成したい場合は、さらにその下にdevelopmentなどのディレクトリを作成します。
SeedFu.fixture_paths
で db/fixtures
のパスを変更できます。
seedファイルは、好きな名前を付けることができ、そしてアルファベット順にロードされます。
次にrakeタスクの説明をします。
rake db:seed_fu
を使ってseedファイルからデータを投入します。
このタスクには二つのオプションがあります。
FIXTURE_PATH
でseedファイルのパスを変更します。
FILTER
で対象のファイルを指定します。複数の場合は、カンマ区切りにします。
seedファイル内で同様のことを書くときは、SeedFu.seed(fixture_paths, filter)
を記述します。
モデル作成
実際にデータ作成をする前にモデルを作成しときます。
$ bundle exec rails g model line name:text
$ bundle exec rake db:migrate
$ bundle exec rails dbconsole
sqlite> .schema lines
CREATE TABLE "lines" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" text, "created_at" datetime, "updated_at" datetime);
データ作成
seedファイルを作成します。
Line.seed do |s|
s.id = 1
s.name = "日比谷線"
end
Line.seed do |s|
s.id = 2
s.name = "千代田線"
end
Line.seed do |s|
s.id = 3
s.name = "丸の内線"
end
DBにデータを入れます。
$ ./bin/rake db:seed_fu
== Seed from db/fixtures/line.rb
- Line {:id=>1, :name=>"日比谷線"}
- Line {:id=>2, :name=>"千代田線"}
- Line {:id=>3, :name=>"丸の内線"}
$ ./bin/rails dbconsole
sqlite> select * from lines;
1|日比谷線|2014-10-13 06:59:21.050282|2014-10-13 06:59:21.050282
2|千代田線|2014-10-13 06:59:21.055116|2014-10-13 06:59:21.055116
3|丸の内線|2014-10-13 06:59:21.056079|2014-10-13 06:59:21.056079
データ更新
id=3のnameを変更してみます。
Line.seed do |s|
s.id = 1
s.name = "日比谷線"
end
Line.seed do |s|
s.id = 2
s.name = "千代田線"
end
Line.seed do |s|
s.id = 3
s.name = "丸ノ内線"
end
$ ./bin/rake db:seed_fu
== Seed from /Users/ko2ic/Sources/ruby/sample/db/fixtures/line.rb
- Line {:id=>1, :name=>"日比谷線"}
- Line {:id=>2, :name=>"千代田線"}
- Line {:id=>3, :name=>"丸ノ内線"}
$ ./bin/rails dbconsole
sqlite> select * from lines;
1|日比谷線|2014-10-13 06:59:21.050282|2014-10-13 06:59:21.050282
2|千代田線|2014-10-13 06:59:21.055116|2014-10-13 06:59:21.055116
3|丸ノ内線|2014-10-13 06:59:21.056079|2014-10-13 07:03:49.738081
id=3だけが更新されていることがわかります。
主キーがidでない場合
まずは、先ほどのモデルの主キーを変更しときます。
今回は、idを無くして、line_idが識別子になるようにします。
class CreateLines < ActiveRecord::Migration
def change
create_table :lines, id: false do |t|
t.string :line_id, null: false
t.string :name
t.timestamps
end
add_index :lines, :line_id, unique: true
end
end
class Line < ActiveRecord::Base
self.primary_key = :line_id
end
$ ./bin/rake db:migrate:reset
$ ./bin/rails dbconsole
sqlite> .schema lines
CREATE TABLE "lines" ("line_id" varchar(255) NOT NULL, "name" varchar(255), "created_at" datetime, "updated_at" datetime);
CREATE UNIQUE INDEX "index_lines_on_line_id" ON "lines" ("line_id");
これで、line_idが識別子のテーブルが出来ました。
次は、seed-fuを使っての主キーがidじゃない場合の指定の方法です。
seedの引数にシンボルを指定するだけです。これは複合キーでもできます。
Line.seed(:line_id) do |s|
s.line_id = 'odpt.Railway:TokyoMetro.Hibiya'
s.name = '日比谷線'
end
Line.seed(:line_id) do |s|
s.line_id = 'odpt.Railway:TokyoMetro.Chiyoda'
s.name = '千代田線'
end
Line.seed(:line_id) do |s|
s.line_id = 'odpt.Railway:TokyoMetro.Marunouchi'
s.name = '丸ノ内線'
end
$ ./bin/rake db:seed_fu
== Seed from ./db/fixtures/line.rb
- Line {:line_id=>"odpt.Railway:TokyoMetro.Hibiya", :name=>"日比谷線"}
- Line {:line_id=>"odpt.Railway:TokyoMetro.Chiyoda", :name=>"千代田線"}
- Line {:line_id=>"odpt.Railway:TokyoMetro.Marunouchi", :name=>"丸ノ内線"}
$ ./bin/rails dbconsole
sqlite> select * from lines;
odpt.Railway:TokyoMetro.Hibiya|日比谷線|2014-10-13 07:46:48.855379|2014-10-13 07:46:48.855379
odpt.Railway:TokyoMetro.Chiyoda|千代田線|2014-10-13 07:46:48.859168|2014-10-13 07:46:48.859168
odpt.Railway:TokyoMetro.Marunouchi|丸ノ内線|2014-10-13 07:46:48.860122|2014-10-13 07:46:48.860122
シンタックスシュガー
以下のようにも記述できます。
Line.seed(:line_id,
{ line_id: 'odpt.Railway:TokyoMetro.Hibiya', name: '日比谷線' },
{ line_id: 'odpt.Railway:TokyoMetro.Chiyoda', name: '千代田線' },
{ line_id: 'odpt.Railway:TokyoMetro.Marunouchi', name: '丸ノ内線' },
)
$ ./bin/rake db:seed_fu
== Seed from /Users/ko2ic/Sources/ruby/sample/db/fixtures/line.rb
- Line {:line_id=>"odpt.Railway:TokyoMetro.Hibiya", :name=>"日比谷線"}
- Line {:line_id=>"odpt.Railway:TokyoMetro.Chiyoda", :name=>"千代田線"}
- Line {:line_id=>"odpt.Railway:TokyoMetro.Marunouchi", :name=>"丸ノ内線"}
更新したくない場合
一度作成した後は、更新したくない場合は、seed_onceを使います。
Line.seed_once(:line_id,
{ line_id: 'odpt.Railway:TokyoMetro.Hibiya', name: '日比谷線' },
{ line_id: 'odpt.Railway:TokyoMetro.Chiyoda', name: '千代田線' },
{ line_id: 'odpt.Railway:TokyoMetro.Marunouchi', name: '丸ノ内線' },
)
seedファイルを圧縮する
seed-fuは rb.gzip
をそのまま扱えます。
$ gzip -9 db/fixtures/line.rb
$ ls db/fixtures/
line.rb.gz
$ ./bin/rake db:seed_fu
== Seed from ./db/fixtures/line.rb.gz
- Line {:line_id=>"odpt.Railway:TokyoMetro.Hibiya", :name=>"日比谷線"}
- Line {:line_id=>"odpt.Railway:TokyoMetro.Chiyoda", :name=>"千代田線"}
- Line {:line_id=>"odpt.Railway:TokyoMetro.Marunouchi", :name=>"丸ノ内線"}
既存のDBからseedファイルを作成する
この例では、Lineテーブルにある値をline_gen.rbファイルとして生成します。
その処理をrakeタスクとして登録しています。
(rakeタスクにする必要もないです。)
まずはrakeタスクを自動生成します。
$ bundle exec rails g task seed-fu-gen
seedファイルを作成するために書き換えます。
namespace :seed_fu_gen do
desc 'generate seed-fu file for line.'
task :line => :environment do |t|
SeedFu::Writer.write('./db/fixtures/line_gen.rb', class_name: 'Line', constraints: [:line_id]) do |w|
Line.all.each do |x|
w << x.as_json(except: [:created_at, :updated_at])
end
end
end
end
作ったタスクを実行します。
$ ./bin/rake seed_fu_gen:line
中身を確認すると生成されていることがわかります。
$ cat db/fixtures/line_gen.rb
# DO NOT MODIFY THIS FILE, it was auto-generated.
#
# Date: 2014-10-13 20:22:31 +0900
# Seeding Line
# Written with the command:
#
# ./bin/rake seed_fu_gen:line
#
Line.seed(:line_id,
{"line_id"=>"odpt.Railway:TokyoMetro.Hibiya", "name"=>"日比谷線"},
{"line_id"=>"odpt.Railway:TokyoMetro.Chiyoda", "name"=>"千代田線"},
{"line_id"=>"odpt.Railway:TokyoMetro.Marunouchi", "name"=>"丸ノ内線"}
)
# End auto-generated file.
CSVファイルから投入
例えば、以下のようなCSVがあるとします。
odpt.Railway:TokyoMetro.Ginza,銀座線
odpt.Railway:TokyoMetro.Tozai,東西線
seedファイルを以下のように記述します。
require 'csv'
csv = CSV.read('db/fixtures/line.csv')
csv.each do |line|
line_id = line[0]
name = line[1]
Line.seed(:line_id) do |s|
s.line_id = line_id
s.name = name
end
end
実行すると登録されます。
$ bundle exec rake db:seed_fu FILTER=line_csv
== Seed from ./db/fixtures/line_csv.rb
- Line {:line_id=>"odpt.Railway:TokyoMetro.Ginza", :name=>"銀座線"}
- Line {:line_id=>"odpt.Railway:TokyoMetro.Tozai", :name=>"東西線"}