ボクココ

個人開発に関するテックブログ

Action Text での Amazon S3 アップロード

ども、@kimihom です。

前回に引き続き Action Text に関して調査を続けている。今回は Amazon S3 へアップロードしたものをテキストエリアの中に表示させてみよう。

Active Storage の設定

Action Text のファイルアップロードは、Active Storage の設定に完全に依存している。てことで、これから設定するのは全て Active Storage の設定となる。 まず、config/storage.yml を編集してみよう。デフォルトでは local の向き先はディスクに保存するようになっているが、S3 に直接あげるように変えてみる。

amazon:
  service: S3
  access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %
  secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_acces
  region: ap-northeast-1
  bucket: mybucket

Rails.application.credentials.dig は、bundle exec rails credentials:edit で暗号化されたパスワード関連をセット、閲覧することができる。この場所に AWS の キーを保存してより安全に管理しようという形だ。Heroku を使っていれば 環境変数に保存することが多いかとは思うが、こちらは環境共通で、重要性の高いものを保存するという使い方で良さそう。

そして、config/environments/development.rb での Active Storage の向き先を :amazon に変えておく。

  # Store uploaded files on the local file system (see config/storage.yml for options).
  config.active_storage.service = :amazon

Gemfile に AWS SDK を取り込むことも忘れずに。

gem "aws-sdk-s3", require: false

S3 へのダイレクトアップロードになるため、S3 側の CORS の設定もしておく必要がある。

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>your-domain.com</AllowedOrigin>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedHeader>Origin</AllowedHeader>
    <AllowedHeader>Content-Type</AllowedHeader>
    <AllowedHeader>Content-MD5</AllowedHeader>
    <AllowedHeader>Content-Disposition</AllowedHeader>
    <MaxAgeSeconds>3600</MaxAgeSeconds>
</CORSRule>
</CORSConfiguration>

改めて app/javascript/packs/application.js で正しく JavaScript ライブラリがロードされているかを確認しておこう。require("@rails/activestorage").start() を呼び出すことで、S3 へのダイレクトアップロードを実現することが可能だ。

require("@rails/activestorage").start()
require("trix")
require("@rails/actiontext")

じゃあ、require("@rails/actiontext") では何をやっているの? という疑問が湧いたので調べてみると、意外とシンプルだった。trix-attachment-add でテキストエリアへのファイルドラッグ&ドロップを検知したら、Active Storage の Direct Upload を使って直接 S3 へアップロードし、結果を Action Text 内に入れるという処理である。

Action Text で作られるテーブルを改めてチェック

Action Text を使うと、3つのテーブルが自動で作られる。これらの違いを改めて確認しておく。

action_text_rich_texts

1つの記事に作成される action_text_rich_texts は 1つのみだ。1つの記事に画像が複数あっても、action_text_rich_texts は一つのレコードとしてまとめられる。record_id に対象の記事ID が保存される。Action Text は一つのテーブルで、他の複数の name(Article#body や Profile#introduction など) を保存できるように設計されている様子。

CREATE TABLE IF NOT EXISTS "action_text_rich_texts" (
    "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
    "name" varchar NOT NULL,
    "body" text,
    "record_type" varchar NOT NULL,
    "record_id" integer NOT NULL,
    "created_at" datetime(6) NOT NULL,
    "updated_at" datetime(6) NOT NULL);

active_storage_blobs

記事を書いている途中でアップロードした時点で保存される。ファイルをアップロードしたけど、後で記事を保存しなかった、というケースでも1レコードとして保存される。1つの記事に複数のactive_storage_blobsが存在することはもちろんある。

CREATE TABLE  IF NOT EXISTS "active_storage_blobs" (
        "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, 
    "key" varchar NOT NULL, 
    "filename" varchar NOT NULL, 
    "content_type" varchar, 
    "metadata" text, 
    "byte_size" bigint NOT NULL, 
    "checksum" varchar NOT NULL, 
    "created_at" datetime NOT NULL);

active_storage_attachments

記事が保存された時点で、画像と記事の紐付けが行われる。こちらも1つの記事に複数の active_storage_attachments が存在することはもちろんある。record_id に記事ID 、blob_id にactive_storage_blobs のレコード ID が保存される。

CREATE TABLE IF NOT EXISTS "active_storage_attachments" (
    "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, 
    "name" varchar NOT NULL,
    "record_type" varchar NOT NULL, 
    "record_id" integer NOT NULL, 
    "blob_id" integer NOT NULL, 
    "created_at" datetime NOT NULL)"

Trix による ContentEditable 制御

Action Text は、Active Storage と Trix をうまく繋げるためのものだと考えるのが良いだろう。Trix は、ContentEditable を華麗に制御する JavaScript ライブラリだ。シンプルな JavaScript で、テキストエリアを操作できる。 地味に ローカルストレージの保存は大事だ。デフォルトの Action Text だと編集中にリロードとか戻るとか押されると、今までの編集が消えてしまう。よくあるブログ執筆ツールのように、「編集時の状態に戻す」ボタンで書いてきたものを取ってこれるようにしておこう。

var element = document.querySelector("trix-editor")
element.editor

// 範囲選択
element.editor.setSelectedRange([0, 1])

// カーソル移動
element.editor.moveCursorInDirection("backward")
element.editor.expandSelectionInDirection("forward")

// テキスト挿入
element.editor.insertString("Hello")
element.editor.insertHTML("<strong>Hello</strong>")
element.editor.insertLineBreak()
element.editor.deleteInDirection("backward")

// undo / redo
element.editor.undo()
element.editor.redo()

// ローカルストレージへ保存
// Save editor state to local storage
localStorage["editorState"] = JSON.stringify(element.editor)
// Restore editor state from local storage
element.editor.loadJSON(JSON.parse(localStorage["editorState"]))

終わりに

今回までの調査で、Action Text 内にドラッグ&ドロップしたファイルを S3 へアップロードすることが可能となった。 実際に S3 に保存されていることを確認しよう。なお今回の S3 意外にも、Active Storage は Microsoft Azure Storage Service, Google Cloud Storage Service もサポートしている。

引き続き Trix の調査を続けていこうと思う。Action Text の根幹技術は Trix だ。また ContentEditable の世界へ足を踏み入れると思うとワクワクする!