ActionTextをRails6がリリースされた瞬間だけつかって満足しているRailsエンジニアの皆様こんにちは。
このブログではActionTextをしっかりと使っています。
そこでOEmbedを使った、各種WebサービスのHTML埋め込みへの対応を行ったのでそのお知らせになります。
後述していますが、ActionTextで埋め込めるのはBlobだけではなく、様々なデータを埋め込める事ができるので、個人的にはとても注目しています。
このブログではActionTextをしっかりと使っています。
そこでOEmbedを使った、各種WebサービスのHTML埋め込みへの対応を行ったのでそのお知らせになります。
後述していますが、ActionTextで埋め込めるのはBlobだけではなく、様々なデータを埋め込める事ができるので、個人的にはとても注目しています。
利用スタック
- ActionText
- ruby-oemned (Gem)
- APIについてはこのGemからOEmbedのAPIリクエストをしています。
- https://github.com/ruby-oembed/ruby-oembed
OEmbedを使ってのHTML埋め込み対応
OEmbedについてはこの記事で詳細に説明することはないのでこちらの記事をご覧ください。
https://syncer.jp/oembed-api-matome
ActionTextにAttachmentを埋め込む場合は、Signed Global ID(以後sgidと呼びます)が必要になるので、専用にRailsモデルを作ってしまうのが現状は楽だと思います。
https://mikerogers.io/2019/08/03/using-signed-global-id-in-rails.html
なので、OEmbedのレスポンス内容を保存しておくモデルを作成して実装を進めます。
https://syncer.jp/oembed-api-matome
ActionTextにAttachmentを埋め込む場合は、Signed Global ID(以後sgidと呼びます)が必要になるので、専用にRailsモデルを作ってしまうのが現状は楽だと思います。
https://mikerogers.io/2019/08/03/using-signed-global-id-in-rails.html
なので、OEmbedのレスポンス内容を保存しておくモデルを作成して実装を進めます。
モデルにActiontext::Attachableをincludeする
ActionTextにモデルのデータを埋め込ませたい場合は、`ActionText::Attachable` モジュールをインクルードする必要があります。
おそらくActiveStorageなんかのBlobモデルでも同じようにこのモジュールがInlcludeされているはずです。
下記のようなモデルを作って実装しました。namespaseは先述のGemと被ってしまうためあえて設定しています。
おそらくActiveStorageなんかのBlobモデルでも同じようにこのモジュールがInlcludeされているはずです。
下記のようなモデルを作って実装しました。namespaseは先述のGemと被ってしまうためあえて設定しています。
class ActionText::Oembed < ApplicationRecord include ActionText::Attachable validates :url, presence: true validates :raw_info, presence: true def html raw_info['fields']["html"] end # raw_infoカラムにJSONのレスポンス内容が保持されている。 def raw_info begin JSON.parse(read_attribute(:raw_info)) rescue => e {} end end end
OEmbedのコンテンツを取得して作成するWebAPI
RailsでOEmbedのHTMLコンテンツを取得して、JavaScriptでエディタにInsertするのが基本的にな流れなので、WebAPIは先述のGemでシュッと作りました。
ActionTextで特別に対応が必要なのはView側なので、Contoller側の詳しい説明については割愛します。
ActionTextで特別に対応が必要なのはView側なので、Contoller側の詳しい説明については割愛します。
class OembedController < ApplicationController def create begin response = OEmbed::Providers.get(params[:url]) rescue OEmbed::NotFound head 422 and return end if @oembed = ActionText::Oembed.create(url: params[:url], raw_info: response.to_json) render :show, status: 201 else render json: @oembed.errors, status: 422 end end end
WebAPIレスポンスにモデルのsgidが必要
ActionTextに特定のモデルをAttachmentとして埋め込む場合はsgidが必要になります。
ActionText必要になるsgidについては、`@oembed.attachable_sgid` で取得ができます。
ActionText必要になるsgidについては、`@oembed.attachable_sgid` で取得ができます。
# app/views/oembed/show.json.jbuilder json.url @oembed.url json.html @oembed.html json.sgid @oembed.attachable_sgid json.raw_info @oembed.raw_info
Trix.AttachmentをWebAPIのレスポンスをもとに挿入する。
ActionTextでは、エディタではTrixが使われていて、
何かを埋め込む場合は`Trix.Attachment` を使って対応します。
https://github.com/basecamp/trix#inserting-a-content-attachment
OEmbedの取得するAPIから埋め込みたいHTMLを取得して、
それをそのままTrixにInsertAttamentします。ActionTextで特別に必要になるのが先述したsgidの設定です。
何かを埋め込む場合は`Trix.Attachment` を使って対応します。
https://github.com/basecamp/trix#inserting-a-content-attachment
OEmbedの取得するAPIから埋め込みたいHTMLを取得して、
それをそのままTrixにInsertAttamentします。ActionTextで特別に必要になるのが先述したsgidの設定です。
import Trix from "trix" import axios from "axios" document.addEventListener("turbolinks:load", () => { if (document.getElementById("blog_content")) { // initEmbbedLink() const button = document.getElementById("LinkCardButton") button.addEventListener("click", e => { e.preventDefault insertLinkcard() }) } }); function insertLinkcard() { const url = prompt("please inser url Oembed"); if (url) { fetchEmbbedLink(url); } } function insertAttachment (data) { const trixElement = document.getElementById("blog_content"); const editor = trixElement.editor const attachment = new Trix.Attachment({ content: data.html, sgid: data.sgid }); editor.insertAttachment(attachment) } function fetchEmbbedLink(url) { axios.get(`/oembed.json?url=${url}`) .then(response => { insertAttachment(response.data) }).catch(error => { console.error(error) }); }
ここで一番注意するべき個所は、「insertAttachment」のあたりです。
ここで実際にJavaScriptのPromptで受け取ったURLのOEmbedのHTMLコンテンツを取得したものを実際にエディタ(ActionText)にInsertしている処理です。
このsgidで、ActionTextがAttachmentに関連するModelレコードがどのModelのどのデータなのかを特定しています。
試してみていただければわかるのですが、ActionTextに埋め込まれたモデルが削除された場合には、
素のまま使っている場合では埋め込まれたデータも表示されなくなります。
ここで実際にJavaScriptのPromptで受け取ったURLのOEmbedのHTMLコンテンツを取得したものを実際にエディタ(ActionText)にInsertしている処理です。
このsgidで、ActionTextがAttachmentに関連するModelレコードがどのModelのどのデータなのかを特定しています。
試してみていただければわかるのですが、ActionTextに埋め込まれたモデルが削除された場合には、
素のまま使っている場合では埋め込まれたデータも表示されなくなります。
埋め込み用のErbテンプレートを作成
ActionTextが埋め込んだOEmbedを表示するためのテンプレートを作成して完成です。
ActionTextはこのERBテンプレートを経由して、埋め込んだモデルデータをRenderしています。
注意としては、今回は、HTMLコードの埋め込みになるので、「エスケープはさせない」ようにしましょう。
ActionTextはこのERBテンプレートを経由して、埋め込んだモデルデータをRenderしています。
注意としては、今回は、HTMLコードの埋め込みになるので、「エスケープはさせない」ようにしましょう。
# app/views/action_text/oembeds/_oembed.html.erb <%== oembed.html %>
落穂拾い
ここまでで、ほとんど完成なのだけど、Railsは何でもかんでもHTMLの挿入を許可しているわけではない。
iframeとscirptタグについてはOEmbedの埋め込みでは必要不可欠なので、initializersにiframeとscriptタグを許可するように設定を追加しましょう。
iframeとscirptタグについてはOEmbedの埋め込みでは必要不可欠なので、initializersにiframeとscriptタグを許可するように設定を追加しましょう。
# config/initializers/html_sanitation.rb Rails::Html::WhiteListSanitizer.allowed_tags << "iframe" Rails::Html::WhiteListSanitizer.allowed_tags << "script"
という感じで作成したものがこの記事で埋め込んでいるツイートなのである。
日本人、ActionTextに関心なさすぎひん??
— |c||❛.ᴗ❛|| にっく (@webuilder240) July 26, 2020
Railsのコアチーム以外で、ActionTextに対して興味を示している日本人は僕だけな気がする...
— |c||❛.ᴗ❛|| にっく (@webuilder240) August 10, 2020
まとめ
というような感じで、これまでやってきたことをダラダラと書いてきたわけですが、
1つ1つはとても簡単で、ActionTextのAttachmentの概念についてある程度理解した状態であれば、
導入まで1時間もかかりませんでした。
僕個人としては、このActionTextのAttachmentの考え方がBlob以外にもいろいろ応用できることにすごく注目していて、そこで便利な利用方法もあるかと思ったので後日ちゃんと記事にしようと思っています。
1つ1つはとても簡単で、ActionTextのAttachmentの概念についてある程度理解した状態であれば、
導入まで1時間もかかりませんでした。
僕個人としては、このActionTextのAttachmentの考え方がBlob以外にもいろいろ応用できることにすごく注目していて、そこで便利な利用方法もあるかと思ったので後日ちゃんと記事にしようと思っています。