Torihaji's Growth Diary

Little by little, no hurry.

rails7 active_storage 導入とそれを使う。by初心者

はじめに

こんにちは、torihaziです。

最近使った rails7の active_storage と呼ばれる機能。

初めて使ったときは右に左にぶつかりながらなんとかという状態だったので

PRの結果帰ってくるまでまっすぐ整備して一本道にできるようにします。

じゃltg

目次

ActiveStorageとは

私の理解は次のとおりです。

これを使えば、モデルにおいて画像を紐づけられるようになる。

です。乱暴かもしれませんが、そう理解しています。

詳しくはマニュアルに丸投げします。

railsguides.jp

使ってみる

環境構築

Docker環境

環境についてはお任せします。

私は以前作ったDocker環境で行います。ちなみにmac + Docker 前提です。

Dockerとはナンジャという方は下記記事を見てくらはいふぇ。笑

Docker を初めてみる (Docker Desktopを入れる) - Torihaji's Growth Diary

以下作り方です。

cd ~/Document
git clone https://github.com/torihazi/rails-docker.git tmp
cd tmp
docker compose up -d --build
docker compose run web rails db:create
docker compose run web rails generate scaffold User firstname:string lastname:string email:string
docker compose run web rails db:migrate

ここまで行ってhttps://localhost:3000/usersにアクセスすると次の通りになると思います。

ActiveStorage 導入

コンテナ内で下記コマンドを実行するとdb/schema.rbが下記のように変わります。

rails active_storage:install
rails db:migrate

db/schema.rb

  create_table "active_storage_attachments", force: :cascade do |t|
    t.string "name", null: false
    t.string "record_type", null: false
    t.bigint "record_id", null: false
    t.bigint "blob_id", null: false
    t.datetime "created_at", null: false
    t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
    t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
  end

  create_table "active_storage_blobs", force: :cascade do |t|
    t.string "key", null: false
    t.string "filename", null: false
    t.string "content_type"
    t.text "metadata"
    t.string "service_name", null: false
    t.bigint "byte_size", null: false
    t.string "checksum"
    t.datetime "created_at", null: false
    t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
  end

  create_table "active_storage_variant_records", force: :cascade do |t|
    t.bigint "blob_id", null: false
    t.string "variation_digest", null: false
    t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
  end

  create_table "tasks", force: :cascade do |t|
    t.string "title"
    t.string "description"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "users", force: :cascade do |t|
    t.string "firstname"
    t.string "lastname"
    t.string "email"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
  add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
end

3つのテーブルが作られます。

  • active_storage_blobs
  • active_storage_attachements
  • active_storage_variant_records

これらテーブルがあることによって先ほど作った Userモデルに画像を紐づけられるようになります。

例えばプロフィール画像です。

もし仮にUserモデルとこれら3テーブルを紐づけるとするならば関係性は次のようになります。

  • Usersテーブルの1レコードは 1つ以上のattachementsと結びつき
  • blobsテーブルの1レコードは 1つ以上の attachementsとvariant_recordsと結びつく

になります。

attachementsテーブルは中間テーブルというものに相当します。

多対多 を解消する目的で使われるテーブルです。

画像の紐付け準備(ファイル編集)

試しに画像を1枚アップロードしてみます。

そのために幾つかファイルを編集します。

1枚の画像をUserモデルに紐づけられるようにapp/models/user.rbに下記を追記します。

class User < ApplicationRecord
  has_one_attached :image (<= ここの名前は :avatarでもなんでも)
end

複数画像を紐づけるならばhas_many_attached :imagesとかです。

やりたい人はそっちで。今回はoneです。

次にコントローラとビューを編集します。

app/controllers/users_controller.rb

~~~
  def create
    @user = User.new(user_params)

    if @user.save
      redirect_to users_url
    else
      render 'new'
    end
  end
~~~
(ファイル末尾, :image追加)
def user_params
    params.require(:user).permit(:firstname, :lastname, :email, :image)
end

app/views/users/index.html.erb(まるっきり変えます)

<p style="color: green"><%= notice %></p>

<h1>Users</h1>

<div id="users">
<table class="design01">
  <tr>
    <th>firstname</th>
    <th>lastname</th>
    <th>email</th>
    <th>Image</th>
  </tr>
  <% @users.each do |user| %>
  <tr>
    <td><%= user.firstname %></td>
    <td><%= user.lastname %></td>
    <td><%= user.email %></td>
    <% if user.image.attached? %>
      <td><%= image_tag user.image %></td>
    <% end %>
   </tr>
<% end %>
</table>
</div>

<%= link_to "New user", new_user_path %>

app/views/users/_form.html.erb

form_withの中に下記を追記
<div>
  <%= form.label :image, style: "display: block" %>
  <%= form.file_field :image %>
</div>

app/assets/stylesheets/application.css

  .design01 {
  width: 100%;
  text-align: center;
  border-collapse: collapse;
  border-spacing: 0;
 }
 .design01 th {
  padding: 10px;
  background: #e9faf9;
  border: solid 1px #778ca3;
 }
 .design01 td {
  padding: 10px;
  border: solid 1px #778ca3;
 }

 .design01 img {
  width: 100px;
  height: auto;
 }

ここまでやってlocalhostにアクセスすると次のようになると思います。

New Userをクリックしたときは以下

ここまで来たらあとはアップロードするだけです。

ファイルアップロード

コンソール立ち上げて rails cから追加してもいいですが今回は サイトの方から行います。

適当な値を入力してアップロードします。

成功すると Indexページに戻り、次の画面が表示されると思います。

これでUserモデルの1レコードに紐づく画像を登録することができました。

余談(Fakerも使ってアップロード)

例えば開発環境やテスト環境でとりあえず数十個一気にぶち込みたいなんていう時があると思います。

そんなときは Fakerというgemを入れて次のようにやってみてください。

1. fakerを入れて各種設定をする。

rails7 seed 日本語 訳して使ってみた - Torihaji's Growth Diary

2. サンプル画像をapp/assets/images配下に配置

3.db/seeds.rbを編集

db/seeds.rb

unless Rails.env.production?
  50.times do
    user = User.new(firstname: Faker::Name.first_name,
                    lastname: Faker::Name.last_name,
                    email: Faker::Internet.email(domain: "gmail.com")
                    )
    user.image.attach(
      io: File.open(Rails.root.join('app/assets/images/450x300.jpg')),
      filename: '450x300.jpg'
    )
    user.save
  end
end

4.コマンド実行

コンテナに入って rails db:seed実行してください。

(気になる人は先ほど追加した1件を削除してから上のコマンド打ってください。私は消してます。)

するとIndexページに50個追加されていると思います。全部は長いので無理です。

気になる人は view ファイルに <%= user.id %>も追加してみてください。

id追加したものがこちらです。50個ありますよね。

ちなみにこれを描画する際に 1 + n問題 というものが発生しています。

今回はサンプルなので無視してます。

気になる人はそれも解決してみてください。

終わりに

いかがだったでしょうか。

active_storageに加えて seedで一気に50個どばっと入れられるようになりました。

まだまだマサラタウンかもしれませんが、これから頑張ります。

初心者なりに頑張ります。

皆さんも頑張ってください。