Rails 初期データ投入(rake db:seed)について(Rails 4.2, Faker, Gimei, Enumerize, activerecord-import)

Railsで初期データを投入する時、`rake db:seed` を叩くと思う。色んな gem や tips を組み合わせることで幸せになれたので、ここにまとめておく。

なお、ここに書いてある内容は、参考ページに書いてある内容がほとんどであり、色々と集約したものになっている。原文記事の作者様には大変感謝である。

# TL;DR

1. 以下の gem を使うと `rake db:seed` が捗るようになった
  - [stympy/faker](https://github.com/stympy/faker#usage%20title:) : ダミーデータ生成(色々)
  - [willnet/gimei](https://github.com/willnet/gimei) : ダミーデータ生成(日本語 姓名、住所)
  - [brainspec/enumerize](https://github.com/brainspec/enumerize) : Enum 定義関連
  - [zdennis/activerecord-import](https://github.com/zdennis/activerecord-import) : バルクインサート/バルクアップデート
2. ついでに `rake db:seed:user` みたいにできるようにしたら `db/seeds.rb` がスッキリした

# 前提
Rails歴が浅いので他にベストプラクティスがあるかも。その場合はぜひ~~マサカリ~~コメントして欲しい。

# 環境
- ruby 2.3.1
- Rails 4.2.7.1(db : mysql)
- faker 1.6.6
- gimei 0.2.0
- enumerize 2.0.1
- activerecord-import 0.17.1

# やったこと

## まずはgemをインストール

```ruby:Gemfile
gem 'enumerize'
gem 'activerecord-import'
group :development, :test do
  gem 'faker'
  gem 'gimei'
end
```

### faker の日本語ロケールファイルを取得

[stympy/faker : faker/lib/locales](https://github.com/stympy/faker/tree/master/lib/locales)

上記ページに [ja.yml](https://github.com/stympy/faker/blob/master/lib/locales/ja.yml) があるので、ダウンロードして `lib/locales/ja.yml` に配置。

## 新規 rake タスクを作成

### `lib/tasks/seed.rake` 作成

`vi lib/tasks/seed.rake` などで `lib/tasks/seed.rake` を作成。

```diff:lib/tasks/seed.rake
+ Dir.glob(File.join(Rails.root, 'db', 'seeds', '*.rb')).each do |file|
+   desc "Load the seed data from db/seeds/#{File.basename(file)}."
+   task "db:seed:#{File.basename(file).gsub(/\..+$/, '')}" => :environment do
+     load(file)
+   end
+ end
```

続いて `vi db/seeds.rb` で `db/seeds.rb` を編集。

```diff:db/seeds.rb
+ Dir.glob(File.join(Rails.root, 'db', 'seeds', '*.rb')) do |file|
+   load(file)
+ end
```

※[コメント](http://qiita.com/17number/items/c500d469a8851d879737#comment-6feec8575b944b800557)で、もっとシンプルな書き方を教えて頂いたので、そちらもあわせてご確認ください。

### `db/seeds` ディレクトリ作成

```bash
$ mkdir db/seeds
```

### rake タスクの確認

まずはそのまま `rake -T db:seed` を打ってみる。

```bash
$ rake -T db:seed
rake db:seed  # Load the seed data from db/seeds.rb
```

次に `db/seeds/hoge.rb` を作ってからもう一度打ってみる。`rake db:seed:hoge` が新しくできているのが分かる。

```bash
$ touch db/seeds/hoge.rb
$ rake -T db:seed
rake db:seed       # Load the seed data from db/seeds.rb
rake db:seed:hoge  # Load the seed data from db/seeds/hoge.rb
```

試しに `db/seeds/hoge.rb` を編集してから `rake db:seed:hoge` と打ってみる。

```bash
$ echo "p 'hoge'" >> db/seeds/hoge.rb
$ cat db/seeds/hoge.rb
p 'hoge'
$ rake db:seed:hoge
"hoge"
```

同様に `db/seeds/fuga.rb` を作ってから `rake db:seed:fuga` および `rake db:seed` を打ってみる。`rake db:seed:fuga` は `db/seeds/fuga.rb` だけが、 `rake db:seed` は `db/seeds/*.rb` が実行されているのが分かる。

```bash
$ echo "p 'fuga'" >> db/seeds/fuga.rb
$ cat db/seeds/fuga.rb
p 'fuga'
$ rake db:seed:fuga
"fuga"

$ rake db:seed
"hoge"
"fuga"
```

これでモデル毎にファイルを分ければ、不要なデータは投入しなくて良いなど、自由度が上がる。

## ダミーデータの投入

### 実際に作ったファイル

まずは自分が作った `db/seeds/user.rb` の内容を晒してみる。`User` モデルにありがちなデータを投入していると思って見て欲しい。

```ruby:db/seeds/user.rb
# coding: utf-8

# Create dummy users
# Male
users = []
50.times do |no|
  gimei = Gimei.male
  users << User.new(
    user_name: Faker::Internet.user_name,
    email: Faker::Internet.email,
    first_name: gimei.first.kanji,
    first_name_kana: gimei.first.hiragana,
    last_name: gimei.last.kanji,
    last_name_kana: gimei.last.hiragana,
    birthday: Faker::Time.between(40.years.ago, 18.years.ago, :all).to_s[0, 10],
    gender: User.gender.male.value,
    univ_id: University.pluck(:id).sample,
  )
end

# Female
50.times do |no|
  gimei = Gimei.female
  users << User.new(
    user_name: Faker::Internet.user_name,
    email: Faker::Internet.email,
    first_name: gimei.first.kanji,
    first_name_kana: gimei.first.hiragana,
    last_name: gimei.last.kanji,
    last_name_kana: gimei.last.hiragana,
    birthday: Faker::Time.between(40.years.ago, 18.years.ago, :all).to_s[0, 10],
    gender: User.gender.female.value,
    univ_id: University.pluck(:id).sample,
  )
end

# Bulk insert
User.import users
```

### ざっくりと解説

コードそのままだが、流れとしては、

1. バルクインサートする用の配列変数 `users` を初期化
2. 50人分の男性データを `users` に追加
3. 50人分の女性データを `users` に追加
4. 100人分のユーザーデータを `User.import users` でバルクインサート

となっている。

#### activerecord-import の活用

もうお分かりの通り、

1. 配列の初期化
2. 配列にデータを追加していく
3. 最後に `Hoge.import hoges`

だけで良い。SQLが1回で済む分、毎回 `User.create` とするより実行速度が早い。

#### Faker, Gimei の活用

今回は使ってないものもあるが、使えそう(良く使いそう)なものを簡単にまとめておく。

| 対象データ   |  利用メソッド | サンプル | 備考 |
|:-----------------|:------------------|:------------------|:------------------|
| ユーザー名       | `Faker::Internet.user_name` | `ollie` |  |
| メールアドレス | `Faker::Internet.email` | `nathen@turcotte.org` |  |
| 姓名(漢字) | `Gimei.kanji` | `石田 弥安` | ※1 |
| 姓名(ひらがな) | `Gimei.hiragana` | `もりた たいさく` | ※1 |
| 姓名(カタカナ) | `Gimei.katakana` | `ホリエ ケイスケ` | ※1 |
| 名(漢字) | `Gimei.first.kanji` | `昇平` | ※1 |
| 名(ひらがな) | `Gimei.first.hiragana` | `ひろえ` | ※1 |
| 名(カタカナ) | `Gimei.first.katakana` | `イクフミ` | ※1 |
| 姓(漢字) | `Gimei.last.kanji` | `石田` | ※1 |
| 姓(ひらがな) | `Gimei.last.hiragana` | `たにがわ` | ※1 |
| 姓(カタカナ) | `Gimei.last.katakana` | `タナカ` | ※1 |
| 日付 | `Faker::Time.between(40.years.ago, 18.years.ago, :all).to_s[0, 10]` | `1977-08-04` | ※2 |
| 時刻 | `Faker::Time.between(40.years.ago, 18.years.ago, :all).to_s[11, 8]` | `23:44:20` | ※2 |
| 住所(都道府県) | `Faker::Address.state` | `山形県` |  |
| 住所(都道府県:漢字) | `Gimei.prefecture.kanji` | `宮崎県` | ※1 |
| 住所(都道府県:ひらがな) | `Gimei.prefecture.hiragana` | `ぎふけん` | ※1 |
| 住所(都道府県:カタカナ) | `Gimei.prefecture.katakana` | `トットリケン` | ※1 |
| 住所(市？) | `Faker::Address.city` | `奈々区` |  |
| 住所(市:漢字) | `Gimei.city.kanji` | `長岡市` | ※1 |
| 住所(市:ひらがな) | `Gimei.city.hiragana` | `いさはやし` | ※1 |
| 住所(市:カタカナ) | `Gimei.city.katakana` | `ヤマモトグンフジサトマチ` | ※1 |
| 住所(町:漢字) | `Gimei.town.kanji` | `黒木町北大淵` | ※1 |
| 住所(町:ひらがな) | `Gimei.town.hiragana` | `いいじまみちひがし` | ※1 |
| 住所(町:カタカナ) | `Gimei.town.katakana` | `カメオチョウ` | ※1 |

※1
`Gimei.kanji` と毎回打つと値が変わるので、`Gimei.kanji` `Gimei.hiragana` `Gimei.katakana` はバラバラになってしまう。同じ名前の情報が欲しい場合は `gimei = Gimei.new` や `gimei = Gimei.male` `gimei = Gimei.female` としてから、 `gimei.kanji` `gimei.hiragana` `gimei.katakana` としてやれば良い。

今回自分が作ったものも、あらかじめ `gimei = Gimei.male` としてからデータを取るようにしている。

※2
DB のフォーマットに合わせて取り出す良い方法が見付からなかったので力技。誰か良い方法を知ってれば教えて欲しい。

※3
Faker は `ja.yml` を編集していけば、全てのデータが日本語化できると思われる。公式ロケールファイルは情報が少ないので、適宜カスタマイズしていくと良い。(ついでにプルリクしてあげると喜ばれそう)

※4
細かいリファレンスはそれぞれ以下を参照して欲しい。

- [willnet/gimei : 使い方](https://github.com/willnet/gimei#使い方)
- [stympy/faker : Faker::Address](https://github.com/stympy/faker/blob/master/doc/address.md)
- [stympy/faker : Faker::Internet](https://github.com/stympy/faker/blob/master/doc/internet.md)
- [stympy/faker : Faker::Name](https://github.com/stympy/faker/blob/master/doc/name.md)
- [stympy/faker : Faker::Time](https://github.com/stympy/faker/blob/master/doc/time.md)
- [stympy/faker : Faker::Date](https://github.com/stympy/faker/blob/master/doc/date.md)


#### Enumerize の活用

モデルに `Enumerize` を使うことを追加。細かい部分は後述の参考ページを見て欲しい。

```ruby:app/models/user.rb
class User < ActiveRecord::Base
  extend Enumerize
  enumerize :gender, in: {:male => 0, :female => 1}, default: :male, predicates: true, scope: true
```

i18n 対応。(今回の話とは関係無し)

```ruby:config/locales/models/users/ja.yml
ja:
  enumerize:
    user:
      gender:
        male: 男性
        female: 女性
```

`Enumerize` の定義を追加すると、`User.gender.male.value` `User.gender.female.value` などで値を取得できる。直接 `0` `1` と書くより可読性が上がる。


# 参考にしたページ

## Faker/Gimei
- [fakerとforgeryを使ってダミーデータを生成する - Qiita](http://qiita.com/saino-katsutoshi/items/29c0918e489b27df637a)
- [ランダムな日本語のデータを生成するGemまとめ - Qiita](http://qiita.com/Peranikov/items/12f1015fc20fa41844b3)

余談だが、以下の記事の通り Faker v1.7.3 でユニーク値の生成(重複の回避)がサポートされたとのこと

- [Ruby | Faker で 重複しないユニークな値を生成する - Qiita](http://qiita.com/YumaInaura/items/a130c0ba433d3c4d926f)

## Enumerize
- [Enumerizeを数値型カラムで使う - Qiita](http://qiita.com/umanoda/items/daced48bd5f69caa4ecf)
- [Ruby (on Rails) で使える enumeration 実装を比較してみた - blog.kymmt.com](http://blog.kymmt.com/entry/ruby-enums-comparison)
- [RailsでEnumerizeの使い方 | Gemの紹介 | DoRuby](https://doruby.jp/users/TM/entries/Rails_Enumerize_)
- [[Ruby on Rails] enumerizeの使い方 | Gemの紹介 | DoRuby](https://doruby.jp/users/oneafter999_on_rails/entries/_Ruby_on_Rails__enumerize_)

## activerecord-import
- [Railsでバルクインサート・アップデート - Qiita](http://qiita.com/ysk_1031/items/d669157225e67d3a40bf)
- [(Rails) BULK INSERT、BULK UPDATEの実装方法 — ALL-IN Tech Blog](http://blog.all-in.xyz/2015/11/09/how-to-bulk-insert-and-update/)
- [大量のデータ挿入・更新にバルクインサート・バックアップデートを利用する - 49hack](http://paranishian.hateblo.jp/entry/2015/11/06/142412)
- [Rails(ActiveRecord)でBULK INSERTする方法 - kurobaraのブログ](http://moonstruckdrops.github.io/blog/2013/11/25/ar-bulk-insert/)

## rake db:seed
- [Railsでseedデータを分割して実行できるようにする - Passion make things more better](http://yu0105teshi.hateblo.jp/entry/2016/08/03/180603)
- [テーブルごとに初期データを登録する - 紅孔雀](http://d.hatena.ne.jp/benikujyaku/20111208/1323334447)

## その他見たけど採用しなかったものたち
好き嫌いとかプロジェクトの方針とかあるので、人によってはこっちの方が良いかもと思い、全部載せておきます。

- [railsで初期データを入れる(seed-fuの使い方) - Qiita](http://qiita.com/ko2ic/items/be96e450a33d631e0059)
[mbleigh/seed-fu](https://github.com/mbleigh/seed-fu) よりも前述のやり方の方が肌に合いそうだったので採用見送り。その内、一回試してみたい。

- [seedファイル名を指定して rake db:seed - u16suzuの blog](http://u16suzu.hatenablog.com/entry/2013/05/31/075228)
`rake db:seed_from_file SEED_FILENAME='hogehoge.rb'` より `rake db:seed:hoge` の方が直感的かなと。

- [FakerとFFakerの比較 - Qiita](http://qiita.com/y_matsuwitter/items/adc832f7bbf9cd55f5d7)
[ffaker/ffaker](https://github.com/ffaker/ffaker) の存在は(たぶんこの記事見て)知って試したのですが、日本語化周りとかでうまくいかずに不採用にした(はず、昔のことなので忘れた…)。

- [Fakerでダミーデータを作成 - Qiita](http://qiita.com/torshinor/items/ef2141d6f3828cf0054f)
この記事書いてる時に [tily/ruby-faker-japanese](https://github.com/tily/ruby-faker-japanese) があるのに気付いた。ただ、最終コミットが6年前なので、[willnet/gimei](https://github.com/willnet/gimei) で結果的に正解だったかな。