Webuilder240

ActionTextでTwitterやYoutubeの埋め込みに対応した

2020-08-10 19:51:27 +0900

ActionText OEmbed Rails Ruby
ActionTextをRails6がリリースされた瞬間だけつかって満足しているRailsエンジニアの皆様こんにちは。

このブログではActionTextをしっかりと使っています。
そこでOEmbedを使った、各種WebサービスのHTML埋め込みへの対応を行ったのでそのお知らせになります。

後述していますが、ActionTextで埋め込めるのはBlobだけではなく、様々なデータを埋め込める事ができるので、個人的にはとても注目しています。

利用スタック


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のレスポンス内容を保存しておくモデルを作成して実装を進めます。

モデルにActiontext::Attachableをincludeする

ActionTextにモデルのデータを埋め込ませたい場合は、`ActionText::Attachable` モジュールをインクルードする必要があります。

おそらく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側の詳しい説明については割愛します。
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` で取得ができます。
# 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の設定です。

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に埋め込まれたモデルが削除された場合には、
素のまま使っている場合では埋め込まれたデータも表示されなくなります。

埋め込み用のErbテンプレートを作成

ActionTextが埋め込んだOEmbedを表示するためのテンプレートを作成して完成です。
ActionTextはこのERBテンプレートを経由して、埋め込んだモデルデータをRenderしています。
注意としては、今回は、HTMLコードの埋め込みになるので、「エスケープはさせない」ようにしましょう。
# app/views/action_text/oembeds/_oembed.html.erb
<%== oembed.html %>

落穂拾い

ここまでで、ほとんど完成なのだけど、Railsは何でもかんでもHTMLの挿入を許可しているわけではない。
iframeとscirptタグについてはOEmbedの埋め込みでは必要不可欠なので、initializersにiframeとscriptタグを許可するように設定を追加しましょう。
# config/initializers/html_sanitation.rb

Rails::Html::WhiteListSanitizer.allowed_tags << "iframe"
Rails::Html::WhiteListSanitizer.allowed_tags << "script"

という感じで作成したものがこの記事で埋め込んでいるツイートなのである。




まとめ

というような感じで、これまでやってきたことをダラダラと書いてきたわけですが、
1つ1つはとても簡単で、ActionTextのAttachmentの概念についてある程度理解した状態であれば、
導入まで1時間もかかりませんでした。
僕個人としては、このActionTextのAttachmentの考え方がBlob以外にもいろいろ応用できることにすごく注目していて、そこで便利な利用方法もあるかと思ったので後日ちゃんと記事にしようと思っています。

関連しそうなブログ