【Rails】Action Textの基本情報と実装方法

はじめに

Rails 6でAction Textという機能が追加されました。Action Textを使えば簡単にリッチテキストエディターを実装することができます。Action TextはTrixエディターを使用しています。Trixエディターは「WYSIWYG」と呼ばれる編集方式を採用しているエディターです。

WYSIWYGとは、見たままのものを実際に作成出力するという言葉のWhat You See Is What You Getの頭文字をとったものであり、WYSIWYGエディターとは編集中の画面に表示されるものと同じものが、最終結果(HTML、印刷結果等)として得られるようなアプリケーションのこと。CMS用語集

例えば、文字を大きくしたいならエディター内の文字を直接大きくしたり、画像を挿入したいならエディター内に画像を直接挿入します。エディター内に表示されているものがそのまま生成されるのが特徴です。

この画像はAction Textの編集画面の例です。エディター内のテキストが大きくなったり太字になっているのがわかります。

Action Textの実装

Railsアプリの作成

以下のコマンドを実行して、新しいRailsアプリを作成します。

$ rails new rails-actiontext

以下のコマンドを実行して、基本的なブログの実装に必要なコントローラーやモデルなどを一括作成します。

$ rails g scaffold Article title:string
$ rails db:migrate

この時点でテストサーバーを起動して確認してみます。

$ rails s

テストサーバーが起動したらhttp://localhost:3000/articleにアクセスし、以下のようにタイトルのみのビューが表示されていればOKです。

Action Textの作成

以下のコマンドを実行して、Action Textをインストールします。

$ rails action_text:install

上記コマンドを実行すると、Action Textで入力されたリッチテキストや挿入された画像を保存するテーブルを作成するためのマイグレーションが作成されます。以下のコマンドを実行して、これらのテーブルをデータベーススキーマに追加します。

$ rails db:migrate

記事のモデルarticle.rbに以下を追記します。本文となるAction Textを追加しています。

article.rb

class Article < ApplicationRecord
  # 以下を追記
  has_rich_text :content
end

記事のコントローラーarticles_controller.rbに以下を追記します。コントローラーのストロングパラメーターに本文を追加しています。

articles_controller.rb

    def article_params
      params.require(:article).permit(:title, :content)
    end

記事の投稿/編集フォーム_form.html.erbまたは_form.html.slimに以下を追記します。フォームに本文となるAction Textを追加しています。

_form.html.erb

<%= form_with(model: article, local: true) do |form| %>
  <div class="field">
    <%= form.label :title %>
    <%= form.text_field :title %>

    <%# 以下を追記 %>
    <%= form.label :content %>
    <%= form.rich_text_area :content %>
  </div>
<% end %>

_form.html.slim

= form_with(model: article, local: true) do |form|
  .field
    = form.label :title
    = form.text_field :title

    / 以下を追記
    = form.label :content
    = form.rich_text_area :content

記事の詳細ページshow.html.erbまたはshow.html.slimに以下を追記します。記事の詳細ページにAction Textで入力した本文を追加しています。

show.html.erb

<%# 以下を追記 %>
<p>
  <strong>Content:</strong>
  <%= @article.content %>
</p>

show.html.slim

/ 以下を追記
p
  strong Content:
  = @article.content

これでAction Textの実装ができました。http://localhost:3000/article/newにアクセスし、以下のように本文が追加されていればOKです。

記事の詳細ページは以下の通りです。編集中に見ていた通りの内容が表示されています。

埋め込み画像の保存先

Action Textに挿入した画像の保存先はActive Storageによって決定されます。Action Textで画像を扱う場合はActive Storageのセットアップを行う必要があります。

テーブル(表)の挿入

Action Textが使用しているTrixエディターはテーブル(表)をサポートしていません。記事でテーブル(表)を挿入したい場合はMarkdownの使用をおすすめしますが、どうしてもAction Textでテーブル(表)を扱いたい場合は以下の手順を行います。

lib/ディレクトリ配下にrichtext/code_blocks/ディレクトリを作成し、その中にhtml_service.rbを作成します。作成したファイルに以下を記述します。

html_service.rb

class Richtext::CodeBlocks::HtmlService
  ALLOWED_HTML_TAGS = ["table", "tr", "td", "th", "col", "pre", "p", "h1", "h2", "h3", "summary", "details", "row", "code"]
  ALLOWED_HTML_ATTRIBUTES = []

  # To Validate HTML tags and protect from bad formatted input
  def self.validate(html)
    errors = []
    html = Nokogiri::HTML::DocumentFragment.parse(html)
    html.search("pre").each do |pre_tag|
      pre_tag_html, pre_tag_errors = self.ensure_well_formed_markup(pre_tag.text)
      errors.push(pre_tag_errors) unless pre_tag_errors.empty?
      inner_html = self.extract_inner_html_from_pre_tag(pre_tag_html)
      inner_html = self.remove_not_allowed_tags_and_attributes(inner_html)
      pre_tag.children.first.replace(Nokogiri::XML::Text.new(
        inner_html,
        pre_tag
      ))
    end
    html = ActionText::Fragment.new(html)
    [html.to_html, errors.flatten.uniq]
  end

  # To parse each code block tag and render it to HTML
  def self.render(rich_text)
    html = Nokogiri::HTML::DocumentFragment.parse(rich_text)
    html.search("pre").each do |pre_tag|
      inner_html = Nokogiri::HTML::DocumentFragment.parse(pre_tag.text)
      inner_html = add_styles_to_tables(inner_html)
      advanced_code_block = "<div class='advanced-code-block'>#{inner_html.to_html}</div>"
      pre_tag.replace(advanced_code_block)
    end
    html.to_html
  end

  private

  def self.ensure_well_formed_markup(html)
    parsed = Nokogiri::XML("<pre>#{html}</pre>")
    [parsed, parsed.errors]
  end

  # To add our bootstrap specific classes to table elements
  def self.add_styles_to_tables(html)
    html.search("table").each do |table|
      table["class"] = "table"
      table.wrap("<div class='table-responsive'></div>")
    end
    html
  end

  def self.extract_inner_html_from_pre_tag(html)
    Nokogiri::XML(html.at("pre").inner_html)
  end

  # To add error messages to mis-formatted HTML
  def self.error_messages(errors)
    readable_message = ->(e) { e.message.split(":")[3].strip rescue "" }
    errors.map {|e| readable_message.call(e) }.uniq.join(", ")
  end
end

config/application.rbに以下を追記します。作成したクラスをRailsアプリの起動時にロードしています。

application.rb

module ApplicationName
  class Application < Rails::Application
    # 以下を追記
    config.autoload_paths += %W(#{config.root}/lib #{config.root}/lib)
  end
end

モデルarticle.rbに以下を追記します。

article.rb

class Article < ApplicationRecord
  # 以下を追記
  def formatted_content
    Richtext::CodeBlocks::HtmlService.render(self.content.to_s).html_safe
  end
end

記事の詳細ページshow.html.erbまたはshow.html.slimを以下のように修正します。

show.html.erb

<p>
  <strong>Content:</strong>
  <%# 以下を修正 %>
  <%= @article.formatted_content %>
</p>

show.html.slim

p
  strong Content:
  / 以下を修正
  = @article.formatted_content

テーブル(表)を含んだ記事を投稿するには以下のように入力します。コードブロックの中にHTMLでテーブルタグを記述します。

この記事を投稿すると以下のように表示されます。

上記の例ではわかりやすいようにテーブルのスタイルを設定しています。この方法では、コードブロックの中に記述したテーブルタグはHTMLとして解釈されレンダリングされます。

この方法のデメリットは以下の通りです。これらのデメリットをよく理解した上で導入を検討してください。

  • コードブロックが本来の用途で使えなくなる。
  • 記事の投稿者はテーブルタグの仕様を理解している必要がある。
  • テーブル(表)の編集時はWYSIWYGとしての特性を活かせない。

まとめ

Action Textを導入することで簡単にリッチテキストエディターを実装することができます。記事をリッチテキストエディターではなくMarkdownで書きたい場合はAction Textではなくマークダウンエディターを実装します。

本記事を参考にして、Action Textの使い方を覚えていただければと思います。

関連記事

【Rails】Railsアプリのデバッグ《マルチデバイス篇》
# はじめに 近年のWebアプリはレスポンシブ対応が当たり前になっています。最低でもPCとスマートフォンに対応したデザイン、ときにはその中間のタブレットに対応したデザインなんかも作成する必要があります。 Webアプリの開発はPCを使って行う [...]
2021年5月23日 13:02
【Rails】Railsアプリのデバッグ《Better Errors篇》
# はじめに Railsアプリの開発中になんらかのエラーが発生すると、デフォルトでは以下のような画面が表示されます(画像をクリックすると拡大します)。 <a class="gallery" data-group="gallery" href [...]
2021年5月22日 19:42
【Rails】Railsアプリのデバッグ《byebug篇》
# はじめに Ruby on Railsに限りませんが、アプリの開発中にはエラーは付き物です。なにかしらのエラーが発生したときに、エラーの原因を特定しエラー箇所を修正することをデバッグと言います。Railsではデバッグの手助けとなる機能があらかじ [...]
2021年5月22日 15:30
【Rails】レンダリング(renderメソッド)でアンカー指定を行う
# はじめに 通常、Railsでアンカー付きのリクエストを発生させるには`redirect_to`を使います。 ```rb redirect_to root_path(anchor: 'target') ``` では、`rende [...]
2021年5月20日 10:47
【Rails】Bundler 2.2.x以降は開発者が適切なプラットフォームを追加する必要がある
# 事象 昔作ったRailsアプリを久しぶりに修正しデプロイしようとしたところ、以下のエラーが出力されました。 ```bash # 実行コマンド Running $HOME/.rbenv/bin/rbenv exec bundle ch [...]
2021年5月18日 16:18