🐻‍❄️

RailsのActiveStorageで理解するポリモーフィズム

2023/02/18に公開

はじめに

Railsを学習しながら、「ポリモーフィズム」って何?? って思った経験があるのは私だけじゃないはず...
ということで、自分なりに調べてみました。
詳しく理解するというより、ざっくりニュアンスを掴むくらいの温度感で見ていただけるとmm

ポリモーフィズムってなに??

多方面から、怒られそうなくらい超絶ざっくりいうと、

ほぼ同じ機能だし、別のクラス(orオブジェクト)にして使いまわそうぜ!!

的な感じだったり、

その機能、今後他のクラスでも使いそうだから、雛形つくっちゃおうぜ!!

的な感じです。

詳しくいうと、OCP(Open Closed Principle)に基づいた、「ソフトウェアの構成要素は拡張に対して開いていて、修正に対して閉じていなければならない」という原則の代表的な実装パターンの1つです。
ActiveStorageで作成したテーブルが、インターフェースとなって、拡張しやすいようになっている理解ですかね。
OCPに関しては、以下の記事がめちゃくちゃわかりやすく解説していたので、ぜひmm
https://zenn.dev/rafael612/articles/5d68b432d219f8

↓↓↓ 以下、具体例 ↓↓↓
ex)Photoクラスには画像をアップロードする機能があり、Movieクラスにも同様に動画をアップロードする機能があるとすると、

両方のクラスにファイルをアップロードするクラスがあるなら、共通化しちゃおうぜ!!

っていうお話です。
DRYを重視するRailsっぽいですね。
この具体例を以下で、RailsのActiveStorageを使った例で解説します。

Railsでのポリモーフィズム例

ActiveStorageの概要

Active Storageは、(中略)ファイルをActive Recordオブジェクトにアタッチする機能を提供します。
(中略)Active Storageは、アプリケーションにアップロードした画像の変形や、PDFや動画などの画像以外のアップロードファイルの内容を代表する画像の生成、任意のファイルからのメタデータ抽出にも利用できます

ActiveStorageは、基本的には以下の2つのモデルで構成されています。

  • ActiveStorage::Blob ⇒ ファイルのメタデータを管理
  • ActiveStorage::Attachment ⇒ ActiveStorageを利用するモデルとBlobとの中間テーブル

ER図でいうと以下になります。

ActiveStorageに関する詳しい情報は、以下を参照くださいmm
https://railsguides.jp/active_storage_overview.html

ActiveStorageの導入

rails newは済んでいる程で進めていきます。

まずは、ActiveStorageをインストールします。(以下のコマンドで、ActiveStorageを使用するためのmigrationファイルの雛形が生成されます)

rails active_storage:install
# => Copied migration 20230218102232_create_active_storage_tables.active_storage.rb from active_storage

rails db:migrate
この時点でのschema
schema.rb
create_table "active_storage_attachments", force: :cascade do |t|
    t.string "name", null: false
    t.string "record_type", null: false
    t.integer "record_id", null: false
    t.integer "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.bigint "byte_size", null: false
    t.string "checksum", null: false
    t.datetime "created_at", null: false
    t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
  end

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

対象モデルの作成

次にPhotoモデルとMovieモデルを作成したいと思います。今回はscaffoldを使っていきたいと思います。
PhotoモデルもMovieモデルもタイトルとファイル本体をカラムに持つとします。
:attachmentでattachment属性を付与することで、ActiveStorageに紐づけられます。

異なる2つのモデル(photo,movie)に、1つのテーブル(active_storage_attachments)が紐づけられているので、active_storage_attachmentsテーブルはポリモーフィック関連付けされているなどと表現したりします。
「同じファイルのアップロード機能だから、違うモデルでも共通化して関連つけしているね」ってことです。

rails g scaffold photo title image:attachment
rails g scaffold movie title image:attachment

rails db:migrate
この時点でのschema
schema.rb
# ~ 略 ~
create_table "movies", force: :cascade do |t|
    t.string "title"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "photos", force: :cascade do |t|
    t.string "title"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

実際にテーブルが共通化されているかを確認するために、scaffoldで生成されたview等を使って、各自、画像と動画をDBに保存してみてください。
するとログには、以下のように各モデルと一緒にActiveStorage用のテーブルにデータが保存されています。
このことから、photoモデルでもmovieモデルでも共通してactive_storage_attachmentsテーブルに保存されていることがわかります。

Photosにデータを追加
Photo Create (0.9ms)  INSERT INTO "photos" ("title", "created_at", "updated_at") VALUES (?, ?, ?)  [["title", "タイトル1"], ["created_at", "2023-02-18 11:30:14.908629"], ["updated_at", "2023-02-18 11:30:14.908629"]]
()
ActiveStorage::Blob Create (0.1ms)  INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "metadata", "byte_size", "checksum", "created_at") VALUES (?, ?, ?, ?, ?, ?, ?)  [["key", "qwond8w30wnpherkerw3uqrerguy"], ["filename", "sample_image.jpeg"], ["content_type", "image/jpeg"], ["metadata", "{\"identified\":true}"], ["byte_size", 7916], ["checksum", "/eM6Q7CNUtfIT9sjtvYg6Q=="], ["created_at", "2023-02-18 11:30:14.959438"]]
ActiveStorage::Attachment Create (0.1ms)  INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES (?, ?, ?, ?, ?)  [["name", "image"], ["record_type", "Photo"], ["record_id", 1], ["blob_id", 1], ["created_at", "2023-02-18 11:30:14.960213"]]
Moviesにデータを追加
 Movie Create (0.3ms)  INSERT INTO "movies" ("title", "created_at", "updated_at") VALUES (?, ?, ?)  [["title", "サンプルムービー1"], ["created_at", "2023-02-18 11:30:48.126934"], ["updated_at", "2023-02-18 11:30:48.126934"]]
 ()
 ActiveStorage::Blob Create (0.1ms)  INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "metadata", "byte_size", "checksum", "created_at") VALUES (?, ?, ?, ?, ?, ?, ?)  [["key", "3nh848g2ljxvf2bs7ild9fe6ka5b"], ["filename", "sample_movie.mp4"], ["content_type", "video/mp4"], ["metadata", "{\"identified\":true}"], ["byte_size", 3523218], ["checksum", "yCZcF5wVIzqoaTLiY+SCKQ=="], ["created_at", "2023-02-18 11:30:48.142923"]]
 ActiveStorage::Attachment Create (0.1ms)  INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES (?, ?, ?, ?, ?)  [["name", "image"], ["record_type", "Movie"], ["record_id", 1], ["blob_id", 2], ["created_at", "2023-02-18 11:30:48.143717"]]

おわりに

ポリモーフィックやポリモーフィズムなどの単語を見聞きすると、やる気がなくなるといった症状をお持ちの方々にニュアンスだけでも伝わりますとありがたいです。
自分で調べながらの記事になりますので、間違っている箇所などありましたら、ぜひご指摘お願いしますmm

Discussion