🍱

【Rails】Seedsデータの具体的な書き方

2023/09/28に公開

機能を増やすほどseedファイルを作ることに苦戦したので、自身が作成したseedの内容を振り返りながら解説したいなと思います。
少しでもお役に立てたら幸いです。

seedとは

以前にも記事を書いていますのでここでは割愛します。
https://zenn.dev/ganmo3/articles/91d0b92ab9c30d

ER図


上記テーブルに基づいてseedを作ります。入れていくデータは次の通りです。

  • 管理者ユーザー
  • 一般ユーザー
  • タグ
  • 投稿(ランダムでタグ付をして、公開・下書き・非公開のステータスを分ける)
  • いいね
  • コメント
  • フォローフォロワー

find_or_create_byメソッド

seedファイルの入れ方は、create でなく find_or_create_byを使用し記載していきます。

create メソッド

create メソッドは、単に新しいレコードを作成するだけで、既存のレコードとの重複を考慮しません。

# createメソッドの例
user = User.create(name: 'Ganmo', email: 'example@example.com')

find_or_create_by メソッド

一方、find_or_create_by メソッドは、指定した条件で既存のレコードを検索し、条件に一致するレコードが見つからない場合に新しいレコードを作成します。このメソッドを使用することで、データベース内での重複を避けることができます。また、新しいレコードを作成する前にバリデーションが実行されるため、バリデーションをパスしないと新しいレコードが作成されません。

# find_or_create_byメソッドの例
user = User.find_or_create_by(email: 'example@example.com') do |u|
  u.name = 'Ganmo'
end

seedファイルの実際の記述

まずは挿入したseedデータです。ブロックごとに解説します。

seeds.rb
# Adminのシードデータ
Admin.find_or_create_by!(email: ENV['ADMIN_EMAIL']) do |admin|
  admin.password = ENV['ADMIN_PASSWORD']
end

# ユーザーアカウントとニックネームの配列
nicknames = ['がんも', 'だいこん', 'たまご', 'はんぺん', '牛すじ']

# ユーザー作成
def find_or_create_user(account, nickname)
  introduction = "よろしくお願いします、#{nickname}です。YouTubeチャンネルを登録するとYouTubeのチャンネルアイコンに変わります。"
  email = "#{account}@2seeds.com"
  password = "123456"
  channel = "https://www.youtube.com/channel/UCUfZCxi6GBN4fam5kiwPUnA"
  status = 0

  # ランダムな日付を生成
  min_days_ago = 1
  max_days_ago = 365
  random_days_ago = rand(min_days_ago..max_days_ago)
  random_date = Time.now - random_days_ago.days

  user = User.find_or_create_by!(email: email) do |u|
    u.account = account
    u.nickname = nickname
    u.password = password
    u.introduction = introduction
    u.channel = channel
    u.status = status
    u.created_at = random_date
    u.updated_at = random_date
  end

  if user.persisted?
    puts "User created successfully: #{user.account}"
  else
    puts "Error creating user:"
    puts user.errors.full_messages
  end
end

# ユーザー作成
nicknames.each_with_index do |nickname, index|
  account = "2seeds#{index + 1}"
  find_or_create_user(account, nickname)
end

# タグの作成
tags = %w(旅行 料理 音楽 スポーツ アート)
tags.each { |tag_name| ActsAsTaggableOn::Tag.find_or_create_by(name: tag_name) }

# 投稿の作成メソッド
def create_posts_for_user_with_ordered_dates(user, count, status_options, tags)
  initial_date = Time.now - (count - 1).days

  count.times do |i|
    title = "投稿#{i + 1}"
    body = "サンプルの投稿#{i + 1}です。新鮮な野菜、チーズ、ハムなどお好みの具材を挟んだホットサンドは、軽食からランチまで幅広く楽しめる美味しい料理です。シンプルな食材で手軽に作れ、サクサクの食感ととろけるチーズの組み合わせが絶妙です。ぜひ自宅で作ってみて、美味しいひとときを楽しんでみてください。"
    link = "https://www.youtube.com/watch?v=eYreBz3S7f8"
    status = status_options.sample
    tag_list = tags.sample(2)

    # 日付を順番に遅らせる
    post_date = initial_date + i.days

    post_params = {
      title: title,
      user_id: user.id
    }

    # 投稿を作成または既存のものを検索
    post = Post.find_or_create_by!(post_params) do |p|
      p.body = body
      p.link = link
      p.status = status
      p.created_at = post_date
      p.updated_at = post_date
      p.tag_list.add(tag_list)
    end

    puts "Creating post with title: #{title}, user_account: #{user.account}, tag_list: #{tag_list}"
  end
end

# ユーザーごとに異なるステータス割り当て
User.where.not(account: 'guest').each do |user|
  statuses = [0] * 12 + [1] * 3 + [2] * 3
  create_posts_for_user_with_ordered_dates(user, 12, statuses, tags)  # 投稿を12個生成
end

# お気に入り投稿の作成
users = User.all
posts = Post.published
users.each do |user|
  favorite_posts = posts.sample(rand(1..3))
  favorite_posts.each do |post|
    PostFavorite.find_or_create_by!(
      user_id: user.id,
      post_id: post.id
    )
  end
end

user_comments = [
  '素敵な投稿ですね。',
  '興味深い情報です。',
  '共感できる内容です。',
  '勉強になります。',
  '面白いです!'
]

users.each do |user|
  posts.each do |post|
    num_comments = rand(0..1)
    next if num_comments.zero?

    comment_text = user_comments.sample
    comment = Comment.find_or_create_by!(
      user_id: user.id,
      post_id: post.id
    ) do |c|
      c.comment = comment_text
    end

    if rand(0..1) == 1
      CommentFavorite.find_or_create_by!(
        user_id: user.id,
        comment_id: comment.id
      )
    end
  end
end

# リレーションシップの作成
users.each do |user|
  following_users = users - [user]
  following_users.shuffle.take(rand(1..4)).each do |following_user|
    Relationship.find_or_create_by!(
      follower_id: user.id,
      followed_id: following_user.id
    )
  end
end

admin

管理者作成
# Adminのシードデータ
Admin.find_or_create_by!(email: ENV['ADMIN_EMAIL']) do |admin|
  admin.password = ENV['ADMIN_PASSWORD']
end

解説:
管理者の初期データの実装。
ポイントとしては、メールアドレスとパスワードを環境変数を通して、セキュリティ対策としています。
機密性の高い情報は公開されないよう注意が必要です。
https://zenn.dev/ganmo3/articles/f4b0b396cfd048

user

ユーザー作成

テーブルは以下の通り。

# ユーザーアカウントとニックネームの配列
nicknames = ['がんも', 'だいこん', 'たまご', 'はんぺん', '牛すじ']

# ユーザー作成
def find_or_create_user(account, nickname)
  introduction = "よろしくお願いします、#{nickname}です。YouTubeチャンネルを登録するとYouTubeのチャンネルアイコンに変わります。"
  email = "#{account}@example.com"
  password = "xxxxxx"
  channel = "https://www.youtube.com/channel/UCUfZCxi6GBN4fam5kiwPUnA"
  status = 0

  # ランダムな日付を生成
  min_days_ago = 1
  max_days_ago = 365
  random_days_ago = rand(min_days_ago..max_days_ago)
  random_date = Time.now - random_days_ago.days

  user = User.find_or_create_by!(email: email) do |u|
    u.account = account
    u.nickname = nickname
    u.password = password
    u.introduction = introduction
    u.channel = channel
    u.status = status
    u.created_at = random_date
    u.updated_at = random_date
  end

  if user.persisted?
    puts "User created successfully: #{user.account}"
  else
    puts "Error creating user:"
    puts user.errors.full_messages
  end
end

# ユーザー作成
nicknames.each_with_index do |nickname, index|
  account = "2seeds#{index + 1}"
  find_or_create_user(account, nickname)
end

解説:

  1. ニックネームリストの作成
nicknames = ['がんも', 'だいこん', 'たまご', 'はんぺん', '牛すじ']

上記のコードでは、作りたいユーザーのニックネームをリストに格納しています。このリストからランダムに選ばれたニックネームが各ユーザーに割り当てられます。

  1. ユーザー作成関数
def find_or_create_user(account, nickname)
# ...
end

find_or_create_user 関数で、ユーザーアカウント情報を生成または検索してデータベースに保存するためのメソッドです。引数としてアカウント名、ニックネームを指定します。

  1. ユーザー情報生成
introduction = "よろしくお願いします、#{nickname}です。YouTubeチャンネルを登録するとYouTubeのチャンネルアイコンに変わります。"
email = "#{account}@example.com"
password = "xxxxxx"
channel = "https://www.youtube.com/channel/UCUfZCxi6GBN4fam5kiwPUnA"
status = 0

この部分では、userテーブルに基づいて各ユーザーのプロフィール情報を生成しています。

  1. ランダムな日付の作成
# ランダムな日付を生成
min_days_ago = 1
max_days_ago = 365
random_days_ago = rand(min_days_ago..max_days_ago)
random_date = Time.now - random_days_ago.days

サイトの仕様上、ユーザーの登録日をばらばらにしたかったのでランダムな日付になるようにしてあげます。
ここでは、最小値が1日前(昨日)から最大値を365日前(1年前)の中で設定されます。

  1. ユーザーアカウントの作成
  user = User.find_or_create_by!(email: email) do |u|
    u.account = account
    u.nickname = nickname
    u.password = password
    u.introduction = introduction
    u.channel = channel
    u.status = status
    u.created_at = random_date
    u.updated_at = random_date
  end

指定されたメールアドレスをもつユーザーアカウントをデータベースから検索します。ユーザーアカウントが見つからない場合、新しいユーザーアカウントが作成されます。

  1. ユーザー作成ループ
    nicknames.each_with_index do |nickname, index|
      account = "2seeds#{index + 1}"
      find_or_create_user(account, nickname)
    end
    

上記のコードは、nicknames リスト内の各ニックネームに対してループ処理を行い、ユーザー情報を作成します。このループを通じて、指定したニックネームを持つユーザーアカウントを作ります。

  1. 成功・エラーメッセージの表示:
    if user.persisted?
      puts "User created successfully: #{user.account}"
    else
      puts "Error creating user:"
      puts user.errors.full_messages
    end
    

作成時に分かりやすいよう、ユーザーアカウントが正常に作られた場合、成功メッセージが表示されるようにします。一方、エラーが発生した場合はエラーメッセージが表示され、詳細なエラー情報が出力されます。

tags

タグ作成
# タグの作成
tags = %w(旅行 料理 音楽 スポーツ アート)
tags.each { |tag_name| ActsAsTaggableOn::Tag.find_or_create_by(name: tag_name) }

解説:

  1. tags 変数に、適当なタグ名のリストを格納します。

  2. tags.each { |tag_name| ... } の部分は、tags リスト内の各タグ名に対してループ処理を行います。

  3. ループ内の ActsAsTaggableOn::Tag.find_or_create_by(name: tag_name) は、指定したタグ名を持つタグをデータベース内で検索します。もし該当のタグが見つからない場合、新しいタグを作成します。

posts

投稿データの作成

テーブルは以下の通り。

# 投稿の作成メソッド
def create_posts_for_user_with_ordered_dates(user, count, status_options, tags)
 # 初期日付を計算します。最新の投稿が現在日付で、それ以前の投稿は日付を遡っています。
  initial_date = Time.now - (count - 1).days

  count.times do |i|
    # 各投稿のタイトル、本文、リンク、ステータス、タグを設定します。
    title = "投稿#{i + 1}"
    body = "サンプルの投稿#{i + 1}です。適当な文章を入力してください。"
    link = "https://www.youtube.com/watch?v=eYreBz3S7f8"
    status = status_options.sample
    tag_list = tags.sample(2) # ランダムなタグを2つ選択します。

    # 日付を順番に遅らせます。
    post_date = initial_date + i.days

    post_params = {
      title: title,
      user_id: user.id
    }

    # 投稿を作成または既存のものを検索
    post = Post.find_or_create_by!(post_params) do |p|
      p.body = body
      p.link = link
      p.status = status
      p.created_at = post_date
      p.updated_at = post_date
      p.tag_list.add(tag_list) # タグを追加します。
    end
    
  # わかりやすくするために作成した投稿情報をコンソールに表示するようにします。
    puts "Creating post with title: #{title}, user_account: #{user.account}, tag_list: #{tag_list}"
  end
end

# ユーザーごとに異なるステータス割り当て
User.where.not(account: 'guest').each do |user|
  statuses = [0] * 12 + [1] * 3 + [2] * 3
  create_posts_for_user_with_ordered_dates(user, 12, statuses, tags)  # 投稿を12個生成
end

解説:

  1. 投稿の作成メソッド
def create_posts_for_user_with_ordered_dates(user, count, status_options, tags)
  initial_date = Time.now - (count - 1).days

  count.times do |i|
    title = "投稿#{i + 1}"
    body = "サンプルの投稿#{i + 1}です。"
    link = "https://www.youtube.com/watch?v=eYreBz3S7f8"
    status = status_options.sample
    tag_list = tags.sample(2)

    post_date = initial_date + i.days

    post_params = {
      title: title,
      user_id: user.id
    }

    post = Post.find_or_create_by!(post_params) do |p|
      p.body = body
      p.link = link
      p.status = status
      p.created_at = post_date
      p.updated_at = post_date
      p.tag_list.add(tag_list)
    end

    puts "Creating post with title: #{title}, user_account: #{user.account}, tag_list: #{tag_list}"
  end
end
  • initial_date は、最新の投稿の日付を計算するために使い、最も新しい投稿は現在の日付で、それ以前の投稿は日付を遡って作り、同じ日付じゃないようにします。

  • count.times ブロックは、指定された回数(count 回)ループします。ループごとに新しい投稿が作られます。

  • 投稿のタイトル、本文、リンク、ステータス、およびタグは、それぞれのループでランダムに設定します。

  • post_params ハッシュには、投稿を作成するための必要なパラメータが格納されています。

  • Post.find_or_create_by! メソッドは、指定された条件に一致する投稿を検索し、見つからない場合は新しい投稿を作成します。これにより、重複する投稿が作成されるのを防ぎます。

  • 生成した投稿情報は、分かりやすいようputs ステートメントを使用してコンソールに出力されます。

  1. ユーザーごとに異なるステータスを割り当てる
User.where.not(account: 'guest').each do |user|
  statuses = [0] * 12 + [1] * 3 + [2] * 3
  create_posts_for_user_with_ordered_dates(user, 12, statuses, tags)
end

このブロックでは、異なるユーザーに異なるステータスを持つ投稿を生成されるようにします。

  • User.where.not(account: 'guest').each ブロックは、"guest" アカウント以外のすべてのユーザーに対してループします。

  • statuses 配列は、各ユーザーに対して割り当てるステータスのリストです。12個のステータス0(公開)、3個のステータス1(下書き)、3個のステータス2(非公開)を生成します。

  • create_posts_for_user_with_ordered_dates メソッドを呼び出し、各ユーザーに対して指定された数の投稿を生成します。このメソッドの引数としてユーザー、投稿数、ステータスのリスト、およびタグが渡されます。

このブロックを実行することで、各ユーザーに対して異なるステータスを持つ投稿が作られます。

post favorite

お気に入りの投稿データの作成
  1. お気に入り投稿の生成
users = User.all
posts = Post.published

すべてのユーザーとpublishedステータス(ステータス:0)のすべての投稿を取得します。

  1. ユーザーごとのお気に入り投稿の生成
users.each do |user|
  favorite_posts = posts.sample(rand(1..3))
  favorite_posts.each do |post|
    PostFavorite.find_or_create_by!(
      user_id: user.id,
      post_id: post.id
    )
  end
end
  • users.each do |user| ブロックは、すべてのユーザーに対してループします。
  • favorite_posts は、ランダムに選択された1から3個の投稿を格納するための配列です。posts.sample(rand(1..3)) の部分は、posts 配列からランダムに1から3個の投稿を選択しています。

このコードを実行することで、各ユーザーがランダムなお気に入り投稿がある状態にします。

comment

コメントデータの作成
user_comments = [
  '素敵な投稿ですね。',
  '興味深い情報です。',
  '共感できる内容です。',
  '勉強になります。',
  '面白いです!'
]

users.each do |user|
  posts.each do |post|
    num_comments = rand(0..1)
    next if num_comments.zero?

    comment_text = user_comments.sample
    comment = Comment.find_or_create_by!(
      user_id: user.id,
      post_id: post.id
    ) do |c|
      c.comment = comment_text
    end

    if rand(0..1) == 1
      CommentFavorite.find_or_create_by!(
        user_id: user.id,
        comment_id: comment.id
      )
    end
  end
end

解説:

  1. ユーザーコメントのリストを作成
user_comments = [
  '素敵な投稿ですね。',
  '興味深い情報です。',
  '共感できる内容です。',
  '勉強になります。',
  '面白いです!'
]

コメントのリストを用意してあげます。ユーザーが投稿に対して行うコメントのテンプレートとして使用します。

  1. ユーザーコメントをランダムに付ける
users.each do |user|
  posts.each do |post|
    num_comments = rand(0..1)
    next if num_comments.zero?

    comment_text = user_comments.sample
    comment = Comment.find_or_create_by!(
      user_id: user.id,
      post_id: post.id
    ) do |c|
      c.comment = comment_text
    end

各ユーザーが各投稿にランダムな数のコメントを付けるようにします。

  • users.each do |user|posts.each do |post| の2つのループが、各ユーザーと各投稿に対する操作を行います。
  • num_comments = rand(0..1) は、ランダムに0または1を選択し、それに基づいてコメントの数を決定します。0の場合、コメントを付けないために next if num_comments.zero? を使用してスキップします。
  • comment_text = user_comments.sample は、前述のコメントリストからランダムにコメントを選択します。これにより、異なるコメントが作成されます。
  • Comment.find_or_create_by! メソッドを使用して、コメントをデータベースに保存します。
  1. コメントへの「いいね」を生成
    if rand(0..1) == 1
      CommentFavorite.find_or_create_by!(
        user_id: user.id,
        comment_id: comment.id
      )
    end
  end
end

この部分は、各コメントに対して「いいね」をランダムに付けるようにしています。

  • rand(0..1) == 1 は、ランダムに0または1を選択し、1の場合にのみ「いいね」を付けるようにします。
  • CommentFavorite.find_or_create_by! メソッドを使用して、「いいね」をデータベースに保存します。

relationships

フォローフォロワーデータの作成
# リレーションシップの作成
users.each do |user|
  following_users = users - [user]
  following_users.shuffle.take(rand(1..4)).each do |following_user|
    Relationship.find_or_create_by!(
      follower_id: user.id,
      followed_id: following_user.id
    )
  end
end

解説:

  • users.each do |user| ループは、各ユーザーに対して、他のユーザー(フォロワー)をランダムに選んでフォローさせることが目的です。
  • following_users = users - [user] は、自分自身を除いた他のユーザーのリストを作成します。自分自身をフォローすることはできないため、自分以外のユーザーをフォロー対象とします。
  • following_users.shuffle.take(rand(1..4)) は、ユーザーが1人から4人の他のユーザーをランダムに選んでフォローするようにします。ランダム性を持たせることで、ユーザー間のフォロワー数が様々になります。
  • Relationship.find_or_create_by! メソッドを使用して、ユーザー間のフォローリレーションシップをデータベースに保存します。

コマンド表

Seedファイルを実行するためのコマンドです。

シナリオ コマンド
Seedファイルを実行 rails db:seed
データベースをリセットしSeedを実行 rails db:reset
本番環境でSeedを実行 rails db:seed RAILS_ENV=production

長くなりましたが以上です。
効果的にSeedファイルが入れられるようになるといいですよね!
読んでいただきありがとうございました。

Discussion