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

railsで初期データを入れる(seed-fuの使い方)

More than 5 years have passed since last update.

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_pathsdb/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ファイルを作成します。

db/fixtures/line.rb
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を変更してみます。

db/fixtures/line.rb
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が識別子になるようにします。

/db/migrate/20141012162955_create_lines.rb
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
/app/models/line.rb
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の引数にシンボルを指定するだけです。これは複合キーでもできます。

db/fixtures/line.rb
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

シンタックスシュガー

以下のようにも記述できます。

db/fixtures/line.rb
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を使います。

db/fixtures/line.rb
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ファイルを作成するために書き換えます。

lib/tasks/seed_fu_gen.rake
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があるとします。

db/fixtures/line.csv
odpt.Railway:TokyoMetro.Ginza,銀座線
odpt.Railway:TokyoMetro.Tozai,東西線

seedファイルを以下のように記述します。

db/fixtures/line_csv.rb
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=>"東西線"}
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
ユーザーは見つかりませんでした