【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】Paranoiaを使用した論理削除(ソフトデリート)
# はじめに Paranoiaは、Railsアプリケーションで論理削除(ソフトデリート)を実現するためのGemです。 論理削除は、データベースのレコードを物理的に削除するのではなく、削除フラグを設定することで「削除済み」とみなす方法です。こ [...]
2024年7月20日 21:33
【Rails】activerecord-multi-tenantを使用したマルチテナントアプリケーションの作成
# はじめに マルチテナントアプリケーションでは、複数の顧客(テナント)が同じアプリケーションを利用するため、データの分離が必要です。 activerecord-multi-tenantは、このようなマルチテナント環境をサポートするための便 [...]
2024年7月18日 16:50
【Rails】RubyとRailsにおけるattr_reader, attr_writer, attr_accessorの概念と使用方法
# はじめに RubyとRailsの開発において、`attr_reader`,`attr_writer`,`attr_accessor`は非常に便利なメソッドです。これらは、クラス内でインスタンス変数に対するゲッターおよびセッターメソッドを簡単に [...]
2024年7月17日 18:11
【Rails】RubyとRailsにおけるyieldの概念と使用方法
# はじめに RubyとRailsにおける`yield`は、メソッドやテンプレートの中で動的にコードブロックを実行する能力を提供し、これによってコードの再利用性と拡張性が大幅に向上します。本記事では、RubyとRailsにおける`yield`の概 [...]
2024年7月17日 13:15
【Rails】AASMを使用してオブジェクトの状態遷移を効率的に管理
# はじめに Railsアプリケーションにおいて、オブジェクトの状態管理は重要な課題の一つです。AASM (Acts As State Machine) gemは、複雑な状態遷移を効率的に管理します。本記事では、AASMの基本的な使い方を解説して [...]
2024年7月16日 18:00
【Rails】RSpec + Swagger + rswagでアプリケーションのAPIをテストおよびドキュメント化する方法
# はじめに Railsアプリケーションの開発において、APIのテストとドキュメント化は重要な要素です。 RSpecはテストフレームワークとして広く利用されており、SwaggerはAPIの設計とドキュメント化を支援します。これらを統合するr [...]
2024年7月16日 14:27
【Rails】mailcatcherを使用して開発環境でメール送信をテストする方法
# はじめに mailcatcherは、開発環境でのメール送信をキャプチャするためのツールです。ローカルで送信されたメールをブラウザ上で簡単に確認できるようにします。mailcatcherをRailsアプリケーションで使用する方法について説明しま [...]
2024年7月15日 16:37
【Rails】impressionistを使用してページビューやクリック数を追跡する方法
# はじめに impressionist Gemを使用してRailsアプリケーションでページビューやクリック数を追跡する方法について説明します。 # 実装方法 ## impressionist Gemのインストール まず、impre [...]
2024年7月15日 14:18