【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アップグレードまとめ
# はじめに Ruby on Railsに限らず、何らかのフレームワークを使ってWebシステムを構築している場合、フレームワークのアップグレード作業は避けて通れません。 一般的にフレームワークはバージョン毎にEOL (End of Life [...]
2022年10月1日 14:32
【Rails】ユーザー登録時に行うメールアドレス認証機能の実装方法
# はじめに ユーザー登録/解除やログイン/ログアウトといった認証機能の導入に「devise」というGemを使っている人は多いと思います。「devise」では以下のように記述するだけで、ユーザー登録時に確認メールを送付しメールアドレス認証を行う機 [...]
2022年9月24日 14:24
【Rails】モデルに列挙型(enum)を定義し、使いこなす方法
# はじめに Railsはモデルでカラム名と同名の列挙型(enum)を定義することで、カラムと列挙型の変数を紐付けることができます。カラムと列挙型の変数を紐付けると、カラムに対して様々な便利な使い方ができるようになります。 本記事では、モデ [...]
2022年9月3日 10:29
【Rails】RailsでCORSとPreflight requestの設定を行う方法
# はじめに RailsアプリをAPIサーバーとして構築するには、CORS (Cross-Origin Resource Sharing)と Preflight requestの設定を行う必要があります。APIサーバーは外部からの要求に対して処理 [...]
2022年8月27日 10:44
【Rails】M1チップ搭載MacでRuby on Railsの開発環境構築
# はじめに M1チップ搭載MacにRuby on Railsの開発環境を構築する手順を記載します。 - MacBook Air (M1, 2020) - macOS Monterey 12.3.1 # Homebrew ## [...]
2022年5月5日 11:56
【Rails】Rakeタスクの基本情報と作成・実行方法
# はじめに Railsには標準でRakeというGemが同梱されています。RakeはRubyで実装されたMake(UNIX系のOSで使用できるコマンド)のようなビルド作業を自動化するツールです。Ruby Make、略してRakeというわけですね。 [...]
2022年3月7日 22:12
【Rails】モデルに外部キーを設定する方法とよく起こるエラー内容について
# はじめに Railsでモデルに外部キーを設定する方法について説明します。 # モデルに外部キーを設定する ## リレーションシップ 今回は1つのブログ記事は複数のコメントを持つ1対多のリレーションシップを例に説明します。現在は` [...]
2022年2月10日 14:18
【Rails】Capybaraのfill_inメソッドを実行すると「既存レコードの内容+指定した内容」がセットされる事象の原因と対処【RSpec】
# はじめに RSpec + Capybaraを使用して、Railsアプリの統合テストを実装しています。とあるモデルの編集画面において、入力フォームの内容を書き換えた上で送信し、レコードが更新されることを確認します。 入力フォームの内容を書 [...]
2022年1月27日 21:22