業務でseedデータを作成する機会があったんですが、実装方法に迷ったので備忘録としてまとめます。
- Ruby 3.0.3
- Rails 6.1.6.1
- ぼくは独学で未経験から従業員300名以上の自社開発企業へ転職しました。
- 実務ではVue.jsとRailsを毎日書いています。
- 初心者や駆け出しエンジニアがつまづくポイントも身をもってよく理解しています。
問題
seedデータを作成する際は、rails db:seed
を何度実行してもデータが重複したりエラーが発生したりしないように実装するべきなんですが、そのやり方が分からず、必要以上に時間をかけてしまいました、、。
解決方法
find_or_initialize_by
メソッドという、Railsで提供されるActiveRecordメソッドを使うことで、この問題を解決することができました。
find_or_initialize_by
メソッドについて、Railsドキュメントには以下の説明がありました。
条件を指定して初めの1件を取得し1件もなければ作成
Railsのコードをみると、find_by
メソッドで条件を指定して検索し、結果がfalseならnew
メソッドを実行しているようです。
これだけだと微妙なので、具体例ベースで考えます。
Userモデルのseedデータ投入例
users_data = [
{ email: 'user1@example.com', name: 'Alice' },
{ email: 'user2@example.com', name: 'Bob' }
]
users_data.each do |user_data|
user = User.find_or_initialize_by(email: user_data[:email])
user.attributes = user_data
user.save! if user.new_record? || user.changed?
end
①find_or_initialize_by
メソッドにより、まず以下の処理が行われます。
user_data[:email]
に一致するレコードがデータベースに存在する場合はそのレコードを返す- 存在しない場合は新しいUserインスタンスを返す
※この時点ではデータベースには保存しません。
②次に、user.attributes = user_data
にて、Userオブジェクトのemail
属性とname
属性がuser_data
から割りあてられます。
③最後に、user.save! if user.new_record? || user.changed?
で、↓の2つのどちらかに当てはまる場合のみ、データが保存されます。
- Userオブジェクトが新しいレコードである
- 既存のレコードだけど
email
かname
に変更がある
④あとはrails db:seed
コマンドを叩けば、seedデータをUserテーブルに反映できます。
また、最初にデータを投入した後に、一部のseedデータを変更した場合でも、再度rails db:seed
を実行すれば、エラーなく更新されます。
捕捉
今回のように「おなじ操作(rails db:seed
)を何度実行してもおなじ結果になる特性」を冪等性と言うそうです。
読み方は「べきとうせい」です。
rails db:seed
以外だと、POST通信のAPIを実装する際などでも冪等性を考慮する必要があります。
パフォーマンスの注意点
find_or_initialize_by
メソッドは便利ですが、、パフォーマンスに要注意です。
大量のデータを扱う場合は各レコードごとにクエリが発行されるため、パフォーマンスに影響を与える可能性があります。
seedデータは基本的に一度投入すればOKですが、頻度が多かったり、あまりにも大量のデータを処理する場合は、insert_all
メソッドなど他の手法を検討することも重要です。
※ただしinsert_all
メソッドはバリデーションがスキップされてしまいます。要件に合わせて使いわけましょう。
まとめ
find_or_initialize_by
メソッドをつかうことで、冪等性を保ちつつseedデータを安全にデータベースへ投入する方法を学びました。
コメント