本ガイドでは、リッチテキストコンテンツの扱いを始めるのに必要なものをすべて提供します。
このガイドの内容:
Action Textは、リッチテキストコンテンツを手軽に処理・表示する機能です。リッチテキストコンテンツは、太字・斜体・色・ハイパーリンクなどの書式設定要素を含むテキストであり、プレーンテキストよりも豊かに表示可能で、構造化されたプレゼンテーションを提供します。Action Textを利用することで、リッチテキストコンテンツを作成してテーブルに保存することも、任意のモデルに添付することも可能になります。
Trixエディタが生成するリッチテキストコンテンツは独自のRichTextモデルに保存され、このモデルはアプリケーションの既存のあらゆるActive Recordモデルと関連付けられます。
あらゆる埋め込み画像(およびその他の添付ファイル)は自動的にActive Storageに保存され、include
されたRichTextモデルに関連付けられます。
Action Textには、Trixと呼ばれるWYSIWYGエディタが含まれています。Trixはリッチテキストコンテンツの作成・編集用の使いやすいインターフェイスをユーザーに提供するためにWebアプリケーションで利用され、テキストの書式設定、リンクや引用の追加、画像埋め込みなど多くの機能が使えるようになります。Trixエディタの利用例について詳しくはTrixエディタのWebサイトを参照してください。
Trixエディタで生成されたリッチテキストコンテンツは、アプリケーションにある既存のActive Recordモデルに関連付け可能な独自のRichTextモデルに保存されます。さらに、埋め込み画像(またはその他の添付ファイル)は、Active Storage(依存関係として追加されます)自動的に保存されてRichTextモデルに関連付けられます。コンテンツをレンダリングするとき、Action Textが最初にコンテンツをサニタイズしてから処理するので、ページのHTMLに直接埋め込んでも安全です。
WYSIWYGエディタのほとんどは、HTMLのcontenteditable
とexecCommand
APIのラッパーです。これらのAPIは、Internet Explorer 5.5でWebページのライブ編集をサポートするためにMicrosoftによって設計されました。これらは最終的にリバースエンジニアリングされて他のブラウザにコピーされました。その結果、これらのAPIは完全な形では仕様化されておらず、ドキュメント化もされていません。また、WYSIWYG HTMLエディタが扱う範囲が広大であるため、ブラウザの実装ごとに独自のバグや癖が存在します。したがって、この不一致は多くの場合JavaScript開発者によって解決されなければなりませんでした。
Trixは、contenteditable
をI/Oデバイスとして扱うことで、こうした不一致を回避します。入力がエディタに送信されると、Trixはその入力を編集用に変換して内部ドキュメントモデルに対する操作を実行してから、そのドキュメントをエディタに再レンダリングします。これにより、Trixは振る舞いをキーストローク単位で完全に制御できるようになり、execCommand
への依存とそれに伴う不一致を回避できます。
Action Textをインストールしてリッチテキストコンテンツを扱えるようにするには、以下を実行します。
$ bin/rails action_text:install
上を実行すると、以下が行われます。
trix
と@rails/actiontext
で利用するJavaScriptパッケージをインストールして、application.js
ファイルに追加します。image_processing
gem(Active Storageで埋め込み画像などの添付ファイルの分析・変換を行う)を追加します。詳しくはActive Storageの概要ガイドを参照してください。action_text_rich_texts
active_storage_blobs
active_storage_attachments
active_storage_variant_records
actiontext.css
を作成します。ここにはTrixスタイルシートも含まれます。_content.html
と、Active Storageの添付ファイル(blob)をレンダリングするための_blob.html
を追加します。続いて以下のようにマイグレーションを実行すると、アプリケーションにaction_text_*
テーブルとactive_storage_*
テーブルが追加されます。
$ bin/rails db:migrate
Action Textのインストールでaction_text_rich_texts
テーブルを作成する場合、ポリモーフィックリレーションシップが使われるため、複数のモデルでリッチテキスト属性を追加可能になります。これは、モデルのClassNameを保存するrecord_type
カラムとレコードのIDを保存するrecord_id
カラムを通じて行われます。
ポリモーフィック関連付けを利用すると、1個の関連付けでモデルを複数の他のモデルに従属させることが可能になります。詳しくはActive Recordの関連付けガイドを参照してください。
したがって、Action Textのコンテンツを含むモデルが識別子としてUUID値を利用する場合は、Action Textの属性を使うすべてのモデルでもUUID値を一意の識別子として使わなければなりません。Action Text用に生成したマイグレーションでも、以下のようにレコードのreferences
行にtype: :uuid
を指定する形で更新する必要があります。
t.references :record, null: false, polymorphic: true, index: false, type: :uuid
本セクションでは、リッチテキストを作成するときに従う必要があるいくつかの設定について解説します。
RichTextレコードは、Trixエディタによって生成されたコンテンツをシリアライズbody
属性に保持します。ここには、Active Storageによって保存される埋め込みファイルへのすべての参照も保持されます。このレコードは、リッチテキストコンテンツを必要とするActive Recordモデルに関連付けられます。この関連付けを行うには、リッチテキストを追加するモデルで以下のようにhas_rich_text
クラスメソッドを配置します。
# app/models/article.rb class Article < ApplicationRecord has_rich_text :content end
Articleモデルのテーブルにcontent
フィールドを追加する必要はありません(has_rich_text
クラスメソッドによって、作成済みのaction_text_rich_texts
テーブルに関連付けられ、モデルにリンクされます)。また、属性名をcontent
以外に変更することも可能です。
has_rich_text
クラスメソッドをモデルに追加したら、そのフィールドでリッチテキストエディタ(Trix)を利用できるようにビューを更新します。これを行うには、ビューのフォームフィールドで以下のようにrich_textarea
メソッドを使います。
<%# app/views/articles/_form.html.erb %> <%= form_with model: article do |form| %> <div class="field"> <%= form.label :content %> <%= form.rich_textarea :content %> </div> <% end %>
これによりTrixエディタが表示され、リッチテキストを作成・更新する機能が提供されます。詳しくはエディタのスタイルを更新する方法で後述します。
最後に、エディタからの更新を受け付け可能にするため、参照する属性を以下のようにpermit
でパラメータとして関連コントローラ内で許可する必要があります。
class ArticlesController < ApplicationController def create article = Article.create! params.expect(article: [:title, :content]) redirect_to article end end
has_rich_text
を利用するクラスの名前を変更する必要が生じた場合は、action_text_rich_texts
テーブル内の対応するすべての行でポリモーフィック型record_type
カラムも更新しなければなりません。
Action Textが依存しているポリモーフィック関連付けでは、クラス名をデータベースに保存する必要があるため、Rubyコードで使われるクラス名とデータがずれないよう常に同期を保つことが重要です。この同期は、保存したデータとコードベース内のクラス参照との一貫性を維持するうえで不可欠です。
ActionText::RichText
のインスタンスは、安全なレンダリングのためにコンテンツがサニタイズ済みなので、ビューのページに直接埋め込み可能です。コンテンツは以下のように表示できます。
<%= @article.content %>
ActionText::RichText#to_s
メソッドはRichTextをHTML安全な文字列に変換しますが、ActionText::RichText#to_plain_text
はHTML安全ではない文字列を返すため、ブラウザでレンダリングすべきではありません。Action Textのサニタイズプロセスについて詳しくは、APIドキュメントのActionText::RichText
クラスを参照してください。
content
フィールドに添付(attached)リソースが存在する場合は、リソースの種別に応じてActive Storageで必要な依存関係をインストールしておかないと正しく表示されない可能性があります。
スタイル上の要件を満たすためにエディタの表示を更新したい場合があります。本セクションでは、その方法について解説します。
デフォルトでは、Action TextはCSSの.trix-content
クラスを宣言した要素内でリッチテキストコンテンツをレンダリングします。これはapp/views/layouts/action_text/contents/_content.html.erb
で設定されます。このクラスの要素のスタイルは、Trixのスタイルシートによって設定されます。
Trixのスタイルのいずれかを更新したい場合は、app/assets/stylesheets/actiontext.css
にカスタムスタイルを追加できます。ここには、Trix用のスタイルシートの完全なセットと、Action Textで必要なオーバーライドの両方が含まれています。
リッチテキストコンテンツの周囲にレンダリングされるHTMLコンテナ要素をカスタマイズするには、インストーラが作成した以下のapp/views/layouts/action_text/contents/_content.html.erb
レイアウトファイルを編集します。
<%# app/views/layouts/action_text/contents/_content.html.erb %> <div class="trix-content"> <%= yield %> </div>
埋め込み画像やその他の添付ファイル(いわゆるblob)に対してレンダリングされるHTMLをカスタマイズするには、インストーラが作成するapp/views/active_storage/blobs/_blob.html.erb
テンプレートを以下のように編集します。
<%# app/views/active_storage/blobs/_blob.html.erb %> <figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>"> <% if blob.representable? %> <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %> <% end %> <figcaption class="attachment__caption"> <% if caption = blob.try(:caption) %> <%= caption %> <% else %> <span class="attachment__name"><%= blob.filename %></span> <span class="attachment__size"><%= number_to_human_size blob.byte_size %></span> <% end %> </figcaption> </figure>
現在のAction Textでは、Active Storage経由でアップロードされた添付ファイル(attachment)と、署名付きGlobalIDにリンクされた添付ファイルをサポートしています。
リッチテキストエディタ内で画像をアップロードするとAction Textが使われ、そしてActive Storageが使われます。ただしActive Storageで使われる依存関係の中には、デフォルトのRailsでは提供されていないものもあります。組み込みのプレビューアを利用するには、これらのライブラリを別途インストールしておく必要があります。
中には必須ではないライブラリもありますが、どのライブラリをインストールすべきかについては、エディタでのアップロードをサポートしたいファイルの種別によって異なります。ユーザーがAction TextやActive Storageを使うときによく遭遇するエラーは、エディタで画像が正しくレンダリングされないことです。このエラーは多くの場合、libvips
依存関係がインストールされていないことが原因です。
イベント名 | イベントのターゲット | イベントのデータ(event.detail ) |
説明 |
---|---|---|---|
direct-upload:start |
<input> |
{id, file} |
ダイレクトアップロードが開始中。 |
direct-upload:progress |
<input> |
{id, file, progress} |
ファイルの保存リクエストが進行中。 |
direct-upload:error |
<input> |
{id, file, error} |
エラーが発生。このイベントがキャンセルされない限りalert が表示される。 |
direct-upload:end |
<input> |
{id, file} |
ダイレクトアップロードが完了。 |
Action Textでは、Active Storage経由でアップロードした添付ファイルを埋め込めるだけでなく、署名済みグローバルIDで解決可能な任意のデータを埋め込むことも可能です。
グローバルIDは、gid://YourApp/Some::Model/id
のような形式を取る、モデルのインスタンスをアプリ全体を通して一意に識別するURIです。グローバルIDは、クラスが異なる様々なオブジェクトを一意に参照する識別子が必要な場合に有用です。
グローバルIDを使う場合、Action Textは添付ファイルに対して署名済みグローバルID(sgid)を使うことを求めます。Railsアプリ内のすべてのActive Recordモデルは、デフォルトでGlobalID::Identification
のconcernをミックスインしているので、署名済みグローバルIDで解決可能であり、したがって、ActionText::Attachable
と互換性があります。
Action Textは、保存時に挿入されたHTMLを参照し、後で最新のコンテンツで再レンダリングできるようにします。これにより、モデルの参照が可能となり、そのモデルのレコードが変更されたときに常に最新のコンテンツを表示できるようになります。
Action Textは、モデルをグローバルIDから読み込み、そのモデルのコンテンツをレンダリングする際にデフォルトのパーシャルパスを用います。
以下は、Action Textの添付ファイルの例です。
<action-text-attachment sgid="BAh7CEkiCG…"></action-text-attachment>
Action Textは、要素のsgid属性をインスタンスに解決することで、埋め込まれた<action-text-attachment>
要素をレンダリングします。解決が成功すると、そのインスタンスはレンダリングヘルパーに渡され、最終的なHTMLが<action-text-attachment>
要素の子孫要素として埋め込まれます。
Action Textの<action-text-attachment>
要素内で添付ファイルとしてレンダリングするには、#to_sgid(**options)
メソッド(これはGlobalID::Identification
concernを介して利用可能になります)を実装するActionText::Attachable
モジュールをinclude
する必要があります。
オプションとして、カスタムのパーシャルパスをレンダリングする#to_attachable_partial_path
メソッドや、欠落したレコードを処理する#to_missing_attachable_partial_path
メソッドも宣言できます。
以下はグローバルIDの利用例です。
class Person < ApplicationRecord include ActionText::Attachable end person = Person.create! name: "Javan" html = %Q(<action-text-attachment sgid="#{person.attachable_sgid}"></action-text-attachment>) content = ActionText::Content.new(html) content.attachables # => [person]
デフォルトでは、<action-text-attachment>
はデフォルトのパーシャルパスを介してレンダリングされます。
以下のUser
モデルを例に詳しく考えてみましょう。
# app/models/user.rb class User < ApplicationRecord has_one_attached :avatar end user = User.find(1) user.to_global_id.to_s #=> gid://MyRailsApp/User/1 user.to_signed_global_id.to_s #=> BAh7CEkiCG…
.find(id)
クラスメソッドを持つ任意のモデルに、GlobalID::Identification
をミックスインできます。Active Recordでは、このサポートは自動的に含まれます。
上記のコードは、モデルのインスタンスを一意に識別するための識別子を返します。
次に、Userモデルのインスタンスにある署名済みグローバルIDを参照する<action-text-attachment>
要素が埋め込まれたリッチテキストを考えてみましょう。
<p>Hello, <action-text-attachment sgid="BAh7CEkiCG…"></action-text-attachment>.</p>
Action Textは、この"BAh7CEkiCG…"というStringを用いてUser
インスタンスを解決し、次にデフォルトのパーシャルパスを用いてコンテンツをレンダリングします。
この場合、デフォルトのパーシャルパスはusers/user
パーシャルになります。
<%# app/views/users/_user.html.erb %> <span><%= image_tag user.avatar %> <%= user.name %></span>
これによって、Action Textで以下のHTMLがレンダリングされます。
<p>Hello, <action-text-attachment sgid="BAh7CEkiCG…"><span><img src="..."> Jane Doe</span></action-text-attachment>.</p>
別のパーシャルをレンダリングするには、以下のようにUser#to_attachable_partial_path
を定義します。
class User < ApplicationRecord def to_attachable_partial_path "users/attachable" end end
次にそのパーシャルを宣言します。Userインスタンスは、パーシャル内のuser
ローカル変数でアクセスできます。
<%# app/views/users/_attachable.html.erb %> <span><%= image_tag user.avatar %> <%= user.name %></span>
Action TextがUserモデルのインスタンスを解決できない場合(レコードが削除されているなど)、デフォルトのフォールバック用パーシャルがレンダリングされます。
添付ファイルが見つからない場合にレンダリングするパーシャルを変更するには、以下のようにクラスレベルのto_missing_attachable_partial_path
メソッドを定義します。
class User < ApplicationRecord def self.to_missing_attachable_partial_path "users/missing_attachable" end end
次にそのパーシャルを宣言します。
<%# app/views/users/missing_attachable.html.erb %> <span>Deleted user</span>
アプリケーションのアーキテクチャが伝統的なサーバーサイドレンダリングパターンに沿っていない場合は、バックエンドAPI(JSONを使うなど)で利用するファイルアップロード用のエンドポイントを自分で用意しなければならない場合があります。このエンドポイントはActiveStorage::Blob
を作成し、以下のようにそのattachable_sgid
を返す必要があります。
{ "attachable_sgid": "BAh7CEkiCG…" }
これで、attachable_sgid
を取得してから<action-text-attachment>
タグを用いてフロントエンドコード内のリッチテキストコンテンツに以下のように挿入できるようになります。
<action-text-attachment sgid="BAh7CEkiCG…"></action-text-attachment>
依存するActionText::RichText
をプリロードしたい場合は、以下のように名前付きスコープを利用できます(リッチテキストフィールド名がcontent
という前提)。
Message.all.with_rich_text_content # 添付ファイルなしで本文をプリロードする Message.all.with_rich_text_content_and_embeds # 本文と添付ファイルを両方プリロードする
Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。
原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨
本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。
Railsガイド運営チーム (@RailsGuidesJP)
Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。