【Rails】seeds.rbでサンプルデータ作成時に画像も一緒に生成する（ActiveStorage使用）

# 環境
macOS: Big Sur Ver11.2.2
Rails: 6.0.0
Ruby: 2.6.5

# 前提
Railsで開発中しており、deviseを使ってユーザー登録関係を実装済み（Userモデル）。
さらに、UserモデルはActiveStorageにより画像を1枚添付できるようになっている（必ず添付しなければならない制約をつけてます）。
モデルのコードはこんな感じで、 has_one_attached と :image属性にpresence: trueが指定されているのがわかります。

```ruby
class User < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :prefecture
  belongs_to :category
  has_one_attached :image

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  # パスワードは半角英数混合で８文字
  validates :password, format: { with: /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]{8}\z/i }
  with_options presence: true do
    validates :shop_name
    validates :address
    validates :business_hours
    validates :holiday
    validates :image
    validates :phone_number, format: { with: /\A0[1-9]\d{0,3}[-(]\d{1,4}[-)]\d{4}\z/ }
    with_options numericality: { other_than: 0 } do
      validates :category_id
      validates :prefecture_id
    end
  end
end


```

# やりたいこと
ユーザーに紐づく画像をトップページに表示させたかったので、サンプルデータを20~30件くらい作って一気に表示させてみようと思いました。
新規登録されたユーザーの画像がトップページにズラッと並ぶ感じです。

# 試したこと①
seeds.rbに以下のとおり記述。

```ruby
20.times do |n|
  user = User.create!(
    shop_name: "お店#{ n + 1 }",
    email: "seed#{ n + 1 }@seed.com",
    password: "pass1234",
    password_confirmation: "pass1234",
    category_id: rand(1..10),
    prefecture_id: rand(1..47),
    address: "仮町仮番地123-1",
    business_hours: "9時〜17時",
    holiday: "月曜",
    phone_number: "090-1234-5678",
    image: open("./db/fixtures/seed.jpg")
end
```
結果、「ファイルが見つからない、、」というエラー。

# 試したこと②

```ruby
20.times do |n|
  user = User.create!(
    shop_name: "お店#{ n + 1 }",
    email: "seed#{ n + 1 }@seed.com",
    password: "pass1234",
    password_confirmation: "pass1234",
    category_id: rand(1..10),
    prefecture_id: rand(1..47),
    address: "仮町仮番地123-1",
    business_hours: "9時〜17時",
    holiday: "月曜",
    phone_number: "090-1234-5678",
  )
  user.image.attach(io: File.open(Rails.root.join('app/assets/images/test.jpg')),
                  filename: 'test.jpg')
end
```
結果、「imageがバリデーションに引っかかりました！」という指摘を受けました。

# 試したこと③

```ruby
20.times do |n|
  user = User.create!(
    shop_name: "お店#{ n + 1 }",
    email: "seed#{ n + 1 }@seed.com",
    password: "pass1234",
    password_confirmation: "pass1234",
    category_id: rand(1..10),
    prefecture_id: rand(1..47),
    address: "仮町仮番地123-1",
    business_hours: "9時〜17時",
    holiday: "月曜",
    phone_number: "090-1234-5678",
    user.image.attach(io: File.open(Rails.root.join('app/assets/images/test.jpg')),
                  filename: 'test.jpg')
end
```
結果、「userに使っているimageというメソッドはないよ」と怒られました。

# 解決策
以下の記述方法を発見し、無事に画像データを持つユーザーのサンプルデータを作成できました。

```ruby
20.times do |n|
  user = User.create!(
    shop_name: "お店#{ n + 1 }",
    email: "seed#{ n + 1 }@seed.com",
    password: "pass1234",
    password_confirmation: "pass1234",
    category_id: rand(1..10),
    prefecture_id: rand(1..47),
    address: "仮町仮番地123-1",
    business_hours: "9時〜17時",
    holiday: "月曜",
    phone_number: "090-1234-5678",
    image: ActiveStorage::Blob.create_and_upload!(io: File.open(Rails.root.join("db/fixtures/seed.jpg")), filename: "seed.jpg")
  )
end
```

# 最後に
ググって出てきた情報の多くは、画像をユーザーを作成した後からattachする場合のやり方で、自分のようにユーザー作成時点で画像が必須という設計とは前提が異なっていました。
なので３つの方法全てがエラーになったんですね。
ちなみに後から画像をattachする方法は、[Railsガイド](https://railsguides.jp/active_storage_overview.html#file-io-objects%E3%82%92%E3%82%A2%E3%82%BF%E3%83%83%E3%83%81%E3%81%99%E3%82%8B)に書いてあります。

解決策のcreate_and_upload!メソッドは[こちら](https://edgeapi.rubyonrails.org/classes/ActiveStorage/Blob.html#method-c-create_and_upload-21)に掲載されています。
あまり見たことがない公式リファレンス？だったので、これは初心者には厳しいな、、と言う印象でした。

この記事が誰かの役に立てば幸いです！

# 補足
seeds.rb ファイルを編集などして、再度サンプルデータを流すときは、以下のコマンドを実行すればデータベースの中身をリセットしてサンプルデータの流し込みまで一括でやってくれます。

```terminal
% rails db:reset
```

# 参考リンク
[seeds.rbの画像データの定義の方法がよく分からない](https://teratail.com/questions/319704)