Rails｜シードデータの読み込み順序を番号で制御するつらみから抜け出そう

**環境**
![Static Badge](https://img.shields.io/badge/rails-v7.1-brightgreen)
![Static Badge](https://img.shields.io/badge/seed_fu--brightgreen)




# はじめに

シードデータの読み込み順序を制御する方法として、番号によるプレフィックスをファイル名につけて管理する方法が一般的だと思います。しかしこの場合、すでに多くの人が経験されているように、頻繁に（その上なんの生産性もない）番号の差し替えが生じることになりますよね。
そうなる前にあまり手間をかけずに管理する方法を提案したいと思います。

今回はseed-fuを用いていますが、基本的な考え方は標準のseedシステムでも同じです。

# 結論

```seed.rb
file_names = [
  'categories.rb',
  'publishers.rb',
  'authors.rb',
  'books.rb'
]

dir = Rails.root.join('db', 'fixtures', 'development', 'seeds')
file_names.each do |file_name|
  puts "== Seed from #{dir}/#{file_name}"
  require "#{dir}/#{file_name}"
end
```
シードデータを読み込む際にディレクトリ直下のシードファイルすべてを呼ぶのではなく、読み込みたいファイルをロジックで制御するようにしたファイルを読み込むように変更する。

# 方法

全体の流れとしては
1. gemの導入
2. コマンドの上書き（これは行わなくても良い）
3. ディレクトリ構成の変更
4. ツラミがなくなるシードファイルの作成

となる

## seed-fuを入れる

```rb:Gemfile
gem 'seed-fu', '~> 2.3', '>= 2.3.9'
```

```:$
bundle install
```

## rails db:seedを上書きする
デフォルトの`db:seed`は利用しないので上書きする。

```:$
rails g task seed
```

```lib/tasks/db/seed.rake
# 好みによってディレクトリを移動する
namespace :db do 
  desc 'db:seedコマンドをdb:seed_fuにする'
  task :seed do
    Rake::Task['db:seed_fu'].invoke # 当記事の参考を参照
  end
end
```

## シードファイルを入れるディレクトリを作成する
seed-fuではfixturesディレクトリ直下に環境名のディレクトリがある場合、fixtures直下に加え、環境名直下のファイルも全て読み込む仕様になっている。そのため、環境名直下にロジックを置く。


```
おそらく一般的であろうプレフィックスのついたディレクトリ構成
app/
  └ db/
    └ fixtures/
      ├ development/
      │  └ 01_categories.rb
      │  └ 02_publishers.rb
      │  └ 03_authors.rb
      │  └ 04_books.rb


```

```
つらみがなくなるディレクトリ構成
app/
  └ db/
    └ fixtures/
      ├ development/
      │  ├ seeds/
      │  │ └ authors.rb
      │  │ └ books.rb
      │  │ └ categories.rb
      │  │ └ publishers.rb
      │  │
      │  └ seed.rb => ここにロジックを入れる
      └ production/
         ディレクトリ構成はdevelopmentと同じ

```
このようなディレクトリ構成にしておくと、development環境で`rails db:seed`を実行したときに`db/fixtures/development/seed.rb`のみが呼ばれる。

## seed.rbで読み込み順序を制御

```db/fixtures/development/seed.rb
file_names = [
  'categories.rb',
  'publishers.rb',
  'authors.rb',
  'books.rb'
]

dir = Rails.root.join('db', 'fixtures', 'development', 'seeds')
file_names.each do |file_name|
  puts "== Seed from #{dir}/#{file_name}"
  require "#{dir}/#{file_name}"
end
```

以上で完了。

## 読み込み順序を変更したいとき

例えば`sub_categories`を追加して`publishers`の前に読み込ませたいとき、今までは

```
01_categories.rb -> 01_categories.rb
new                 02_sub_categories.rb
02_publishers.rb -> 03_publishers.rb
03_authors.rb    -> 04_authors.rb
04_books.rb      -> 05_books.rb
```
と番号を変えていたが

```seed.rb
file_names = [
  'categories.rb',
  'sub_categories.rb',
  'publishers.rb',
  'authors.rb',
  'books.rb'
]
```
と配列の中身を変更すればよいだけになる。

## 共通のシードデータが必要になった場合
:::note info
ここから先は記憶を頼りに記述しており、動作確認はしていないことに注意してほしい。
:::


上記構成では、番号の差し替えは発生しなくなったものの、複数の環境で共通の情報をもったシードファイルを流し込みたい場合には、それぞれの環境ディレクトリで同じファイルを作成することになる。

当然のことであるが怠惰代表の威信にかけて、そのようなことは決して行ってはならず、共通のファイルを読み込む設定を導入するべきである。その場合は

```
改善後のディレクトリ構成
app/
  └ db/
    └ fixtures/
      ├ commons/ => 新規で作成    
      ├ development/
      │  ├ seeds/
      │  │ └ authors.rb
      │  │ └ books.rb
      │  │ └ categories.rb
      │  │ └ publishers.rb
      │  │
      │  └ seed.rb
      └ production/

```
として共通のシードファイルを配置するディレクトリを作成。

利用方法の例として、今回は`categories.rb`をdevelopment、productionの両方で呼び出すこととする。
`app/db/fixtures/commons/categories.rb`を作成し中身に下記のようなクラスメソッドを定義する。
```.rb
class Categories
  def self.seed
    Category.seed(:name, 
                  {id: 1, name: 'テクノロジー'},
                  {id: 2, name: 'ポエム'}
                  )
  end
end
```

次に、`development/seeds/categories.rb`、`produciton/seeds/categories.rb`それぞれで先ほど作成した共通ファイルを読み込む
```.rb
require_relative '../../commons/categories'

Categories.seed
```
このように変更することにより、環境以下の`categories.rb`が読み込まれた際にseedが実行され共通のシードデータが反映される。


## 最終的な構成
```
app/
  └ db/
    └ fixtures/
      ├ commons/
      │  └ categories.rb
      ├ development/
      │  ├ seeds/
      │  │ └ authors.rb
      │  │ └ books.rb
      │  │ └ categories.rb
      │  │ └ publishers.rb
      │  │
      │  └ seed.rb
      └ production/ developmentと同じ構成

```

# 参考
https://stackoverflow.com/questions/5641747/rails-how-to-run-rake-task