📝

[Rails][ActiveStorage] has_many_attached 使用メモ

2023/11/12に公開

ActiveStorageについて

Blobオブジェクトを保存する際に使用します。
has_many_attachedの使用は初めてだったのでメモとして残します。

詳細はRailsガイドを参照。
https://railsguides.jp/active_storage_overview.html

has_many_attachedについて

1レコードに複数Blobオブジェクトレコードが紐付く形で以下みたいな構造イメージです。

class Post < ApplicationRecord
  has_many_attached :files
end

# Postレコードイメージ
{ id: 1, title: "とんこつラーメン" }

# Blobレコードイメージ(Post.find(1).filesのポリモーフィック部分)
{ id: 1, post_id: 1, filename: "王道とんこつラーメン写真", .. }
{ id: 2, post_id: 1, filename: "邪道とんこつラーメン写真", .. }
{ id: 3, post_id: 1, filename: "二刀流とんこつラーメン写真", .. }

Blobオブジェクト一覧取得について

JSON返却する際はBlobオブジェクト一覧取得で
どの関連オブジェクトid(posts.id)が紐付いているか表示するには以下のようにマッピングが必要になります。

post = Post.find(1)
post.files.blobs.map do
  {
    id: _1.id,
    post_id: post.id
    filename: _1.filename
  }
end

複数Blobオブジェクト更新について

例えばhas_many_attachedを使用していると
Blobオブジェクト1,2,3が登録されている状態で、
Blobオブジェクト4,5を追加したいという以下みたいなケースがあります。

# 変更前
{ id: 1, post_id: 1, filename: "王道とんこつラーメン写真", .. }
{ id: 2, post_id: 1, filename: "邪道とんこつラーメン写真", .. }
{ id: 3, post_id: 1, filename: "二刀流とんこつラーメン写真", .. }

# 変更後
{ id: 1, post_id: 1, filename: "王道とんこつラーメン写真", .. }
{ id: 2, post_id: 1, filename: "邪道とんこつラーメン写真", .. }
{ id: 3, post_id: 1, filename: "二刀流とんこつラーメン写真", .. }
{ id: 4, post_id: 1, filename: "邪道4とんこつラーメン写真", .. }
{ id: 5, post_id: 1, filename: "二刀流5とんこつラーメン写真", .. }

以下のような順番で処理を実行した場合に
1.初回Blob登録の内容が消されて保存されます。
(知っていればなんてことないですが知らないと少しびっくりします)

# 1.初回Blob登録
Post.new({
  post_id: 1,
  files: [
    { filename: "王道とんこつラーメン写真", .. },
    { filename: "邪道とんこつラーメン写真", .. },
    { filename: "二刀流とんこつラーメン写真", .. }
  ]
}).save!

# 2.更新Blob更新
Post.new({
  post_id: 1,
  files: [
    { filename: "邪道4とんこつラーメン写真", .. },
    { filename: "二刀流5とんこつラーメン写真", .. }
  ]
}).save!

# 変更後の最終レコード
{ id: 4, post_id: 1, filename: "邪道4とんこつラーメン写真", .. }
{ id: 5, post_id: 1, filename: "二刀流5とんこつラーメン写真", .. }

attachしてから実行すると期待通りに動作します。

# 1.初回Blob登録
Post.new({
  post_id: 1,
  files: [
    { filename: "王道とんこつラーメン写真", .. },
    { filename: "邪道とんこつラーメン写真", .. },
    { filename: "二刀流とんこつラーメン写真", .. }
  ]
}).save!

# 2.更新Blob更新
post = Post.find(1)
post.files.attach(
  [
    { filename: "邪道4とんこつラーメン写真", .. },
    { filename: "二刀流5とんこつラーメン写真", .. }
  ]
)
post.save!

# 変更後の最終レコード
{ id: 1, post_id: 1, filename: "王道とんこつラーメン写真", .. }
{ id: 2, post_id: 1, filename: "邪道とんこつラーメン写真", .. }
{ id: 3, post_id: 1, filename: "二刀流とんこつラーメン写真", .. }
{ id: 4, post_id: 1, filename: "邪道4とんこつラーメン写真", .. }
{ id: 5, post_id: 1, filename: "二刀流5とんこつラーメン写真", .. }

ActiveStorageのバリデーションについて

だいたいBlobオブジェクトを保存する際に以下3つは最低制限をかけるかと思います。

  • ファイルサイズ
  • ContentType
  • ファイル拡張子

現状デフォルトのバリデーションでBlobオブジェクトのバリデーションは設定できないので、
自前でバリデーションを書くか、Gemを導入するかになっています。。。

自前バリデーションの場合

class Post < ApplicationRecord
  has_many_attached :files

  ALLOOWED_FILE = [
    ".xls",
    ".xlsm",
    ".xlsx",
    ".xltm"
  ]

  validate :valid_file_type

  private

  def valid_file_type
    error_file = files.blobs.any? { ALLOOWED_FILE.exclude?(_1.content_type) }
    errors.add(:base, "エラーメッセージ") if error_file
  end
end

Gemのバリデーションの場合

https://github.com/igorkasyanchuk/active_storage_validations
https://github.com/aki77/activestorage-validator

Discussion