当ブログではPRや広告を掲載しています

【Rails】seedsで重複レコードを防ぐ!〜find_or_create_by〜

プログラミング
この記事は…
  • マスタデータ複数回投入でレコード重複する問題を解消
  • seedsの様々な書き方を紹介
  • rails初心者〜中級者向け
スポンサーリンク

テーブルの準備

今回の解説のために使用するテーブル(モデル)を作成していきます!

モデル作成

ターミナルでプロジェクトのフォルダに遷移し、以下のコマンドを実行してモデルとマイグレーションファイルを生成します。

rails g model category

実行後にいくつもファイルが作成されますが、今回使用するのはこれらです。

  • db/migrate/YYYYMMDDhhmmss_create_categories.rb
  • db/seeds.rb

マイグレーションファイル修正

先ほど生成されたマイグレーションファイルを以下のように修正して nameカラムを追加します。
timestamps は有っても無くてもOKです。

db/migrate/YYYYMMDDhhmmss_create_categories.rb
class CreateCategories < ActiveRecord::Migration[X.X]
  def change
    create_table :categories do |t|
      t.string :name, null: false

      t.timestamps
    end
  end
end

マイグレーション実行

以下のコマンドでマイグレーションを実行します。

rails db:migrate

成功すると、Categoryテーブルが作成され、スキーマファイルに次の内容が追加されます。

db/schema.rb
create_table "categories", force: :cascade do |t|
  t.string "name", null: false
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end

これでテーブルの準備は完了です!

seedsファイル

レコード作成

通常のレコード作成では “new” もしくは “create” メソッドを使用してレコードを作成します。
今回は “create” を使用していきます。

seedsファイルに以下の内容を追記します。

db/seeds.rb
Category.create([
  {name: "水"},
  {name: "食べ物"},
  {name: "お酒"},
  ])

そして次のコマンドを実行してレコードを作成します。

rails db:seed

‘rails c’ などで実際にレコードが作成されたことが確認できます。

デリート&インサート

ここで、レコードの削除と作成を繰り返すとidがインクリメントされていってしまうという問題があります。

‘rails c’ で確認すると、さきほど新規で作成した時のレコードidはイチから始まっていましたが、削除と作成を繰り返すうちにidが8から始まってしまっていることがわかります。

これは、DBシステムの裏側で持っているidがインクリメントされ、一度使われたidを再利用しないような設定がデフォルトでされているためです。

しかしこれを解決する方法は単純で、idを指定してあげればOKです。

seedsファイルを以下のように修正しましょう。

db/seeds.rb
Category.create([
  {id: 1, name: "水"},
  {id: 2, name: "食べ物"},
  {id: 3, name: "お酒"},
  ])

これでデリート&インサートによるidの変更は無くなりました。

しかし、この状態で複数回seed実行しようとすると以下のようなエラーになってしまいます。

SQLite3::ConstraintException: UNIQUE constraint failed

これは、テーブルのレコードにおいて、idはユニークである必要がありますが、idを指定したためユニークにならないため、その旨のエラーが出ています。

レコードがなければ作成されるようにする

上記の事象を回避するためには、seeds実行時に、既に同じnameのレコードが存在すればスキップし、無ければ作成するという条件分岐ができればOKです。

しかし、1レコード作成ごとに find して if 文を書くのは大変ですし、少しダサいですよね。

そこで「find_or_create_by」というrailsが提供しているメソッドを使用します!

find_or_create_by

このメソッドは、既に同じレコードが存在していればスキップし、存在していないレコードは新規作成します。

単純に書くと以下のように記述できます。が、少しカッコ悪いですね。。
(これではレコードが増えた時にも find_or_crate_by を増やさなくてはなりません)

db/seeds.rb
Category.find_or_create_by({id: 1, name: "水"})
Category.find_or_create_by({id: 2, name: "食べ物"})
Category.find_or_create_by({id: 3, name: "お酒"}

配列を用いてコードを工夫してみます。

db/seeds.rb
categories = [
  {id: 1, name: "水"},
  {id: 2, name: "食べ物"},
  {id: 3, name: "お酒"}
]

categories.each do |category|
  Category.find_or_create_by(category)
end

これでseedsを複数回実行してもエラーが出ず、レコードも重複しないように出来ました!

まとめ

  • マスタレコードの定義は id まで定義する
  • 重複レコード防止のために find_or_create_by メソッドを使用する
  • seeds は配列を用いて書き方を工夫する

コメント