mcpField
A
action-text

action-text

github.com/thinkoodle開発7187 回インストール8 週間前に更新
リポジトリ

action-text について

Rails 開発者向けに、Action Text のリッチテキスト項目、エディター、添付ファイル、安全な表示、テストの実装方法を案内するスキルです。Rails のバージョンに応じた Lexxy と Trix の使い分けや、N+1 クエリを避けるプリロードも説明します。

主な機能

  1. 01Action Text の導入と設定
  2. 02Lexxy または Trix による編集
  3. 03安全な表示とプレーンテキスト抽出
  4. 04Active Storage による添付ファイル管理
  5. 05カスタムオブジェクトの埋め込み
  6. 06N+1 クエリ回避のためのプリロード

活用シーン

  1. 01Rails モデルにリッチテキスト項目を追加
  2. 02フォームにリッチテキストエディターを導入
  3. 03記事内に画像やファイルを表示
  4. 04一覧ページのリッチテキストクエリを最適化

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/thinkoodle/rails-skills --skill action-text

インストール後、Claude Code で/action-textを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
attachables.md
editors.md
security.md
styling.md
testing.md

詳細

Rails Action Text エキスパート

Lexxy エディター(Rails 8.1 以降)または Trix(Rails 6~8.0)を使用して、Rails アプリケーションでリッチテキスト編集を実装します。

主な概念

Action Text は、ポリモーフィック関連付けを介してリッチテキストを別の action_text_rich_texts テーブルに保存します。モデルのテーブルには保存されません。サニタイズ、レンダリング、添付ファイル(Active Storage 経由)、埋め込みオブジェクト(Signed GlobalIDs 経由)を処理します。

エディターの歴史:

  • Trix — Action Text のオリジナルエディター(Rails 6 以降)です。現在も動作し、サポートされています。
  • Lexxy — Basecamp による最新の代替エディター(Rails 8.1 以降)です。ダークモードの改善、CSS カスタムプロパティ、優れた UX を備えています。新しいプロジェクトでは Lexxy を使用してください。

このスキルを使う場面

  • モデルにリッチテキストフィールドを追加する
  • 新規または既存の Rails アプリで Action Text をセットアップする
  • Lexxy または Trix エディターの外観を設定する
  • リッチテキストコンテンツを安全にレンダリングする
  • リッチテキスト内の画像/ファイル添付を処理する
  • カスタム添付可能オブジェクトを作成する(ユーザー、製品などを埋め込む)
  • リッチテキストの N+1 クエリを修正する
  • エディターとレンダリングされたコンテンツのスタイルを設定する
  • リッチテキスト機能をテストする

手順

ステップ 1:Action Text がインストールされているか確認する
# Check for Action Text tables
bin/rails runner "puts ActiveRecord::Base.connection.table_exists?('action_text_rich_texts')"

# Check for Action Text config files
ls app/views/layouts/action_text/contents/_content.html.erb 2>/dev/null
ls app/views/active_storage/blobs/_blob.html.erb 2>/dev/null

# Check Gemfile for editor
grep -E "trix|lexxy" Gemfile

インストールされていない場合は、先にインストールを実行します(ステップ 2)。すでにインストールされている場合は、ステップ 3 に進みます。

ステップ 2:Action Text をインストールする
bin/rails action_text:install
bin/rails db:migrate

次のものが作成されます:

  • action_text_rich_texts テーブル用のマイグレーション(不足している場合は Active Storage のテーブルも作成)
  • エディター用の JavaScript インポート
  • app/views/layouts/action_text/contents/_content.html.erb — コンテンツラッパーの部分テンプレート
  • app/views/active_storage/blobs/_blob.html.erb — 添付ファイルをレンダリングする部分テンプレート
  • app/assets/stylesheets/actiontext.css — デフォルトのスタイル

Lexxy(Rails 8.1 以降)の場合は、次も追加します:

# Gemfile
gem "lexxy"
bundle install

重要:Lexxy のスタイルシートの読み込み順序:

<%# app/views/layouts/application.html.erb %>
<%# Lexxy FIRST, then app styles (so your overrides win) %>
<%= stylesheet_link_tag "lexxy", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>

エージェントによくある間違い: bin/rails action_text:install の実行を忘れることです。実行しないと、マイグレーション、JS インポート、ビューパーシャルが作成されません。has_rich_text の宣言だけでは不十分です。

ステップ 3:モデルにリッチテキストを追加する
# app/models/article.rb
class Article < ApplicationRecord
  has_rich_text :content
end

主なポイント:

  • articles テーブルにカラムは不要です。コンテンツは action_text_rich_texts に保存されます。
  • 属性名は任意です(:content、:body、:description など)。
  • 1 つのモデルに複数のリッチテキスト属性を設定できます:has_rich_text :body と has_rich_text :summary
  • has_rich_text ごとに、個別の ActionText::RichText レコードが作成されます。
ステップ 4:フォームにエディターを追加する
<%# app/views/articles/_form.html.erb %>
<%= form_with model: @article do |form| %>
  <div class="field">
    <%= form.label :content %>
    <%= form.rich_text_area :content %>
  </div>
<% end %>

コントローラーで属性を許可します:

class ArticlesController < ApplicationController
  def create
    @article = Article.create!(article_params)
    redirect_to @article
  end

  private

  def article_params
    params.expect(article: [:title, :content])
  end
end

注: rich_text_area(または rich_textarea)は、どちらも使用できます。リッチテキストコンテンツは単一の文字列パラメーターです。特別なネストは必要ありません。

ステップ 5:リッチテキストコンテンツをレンダリングする
<%# Safe — Action Text sanitizes content automatically %>
<%= @article.content %>

以上です。ActionText::RichText#to_s は、直接埋め込んでも安全なサニタイズ済み HTML を返します。

プレーンテキストの場合(抜粋、メタディスクリプションなど):

@article.content.to_plain_text
# => "Hello world. This is bold text."

# Truncated excerpt
truncate(@article.content.to_plain_text, length: 150)

コンテンツの有無を確認する:

@article.content.blank?  # true if no content
@article.content.present? # true if has content
ステップ 6:エディターとコンテンツのスタイルを設定する
Lexxy(Rails 8.1 以降)

エディターのサイズ設定:

lexxy-editor,
.lexxy-editor {
  min-height: 300px;
}

レンダリングされたコンテンツのスタイル設定(.lexxy-content ラッパーを使用):

<%# app/views/layouts/action_text/contents/_content.html.erb %>
<div class="lexxy-content">
  <%= yield %>
</div>
.lexxy-content {
  line-height: 1.6;
  overflow-wrap: break-word;
}

.lexxy-content p { margin: 0 0 1rem; }
.lexxy-content h1, .lexxy-content h2, .lexxy-content h3 {
  font-weight: 600;
  line-height: 1.3;
  margin: 1.5rem 0 0.75rem;
}
.lexxy-content ul, .lexxy-content ol {
  margin: 0 0 1rem;
  padding-left: 1.5rem;
}
.lexxy-content blockquote {
  border-left: 3px solid var(--color-border);
  margin: 1rem 0;
  padding: 0.5rem 0 0.5rem 1rem;
  color: var(--color-ink-muted);
}
.lexxy-content code {
  font-size: 0.875em;
  background: var(--color-surface-muted);
  padding: 0.125rem 0.375rem;
  border-radius: 4px;
}
.lexxy-content pre {
  background: var(--color-surface-muted);
  border-radius: 8px;
  padding: 1rem;
  overflow-x: auto;
  margin: 1rem 0;
}

CSS カスタムプロパティを使ったダークモード:

:root {
  --lexxy-color-canvas: var(--color-surface);
  --lexxy-color-text: var(--color-ink);
  --lexxy-color-link: var(--color-link);
  --lexxy-color-code-bg: var(--color-surface-muted);
  --lexxy-focus-ring-color: var(--color-primary);
}

Lexxy の CSS 変数一覧については、references/editors.md を参照してください。

Trix(Rails 6~8.0)

コンテンツラッパーには .trix-content を使用します:

<%# app/views/layouts/action_text/contents/_content.html.erb %>
<div class="trix-content">
  <%= yield %>
</div>

app/assets/stylesheets/actiontext.css でスタイルを上書きします。

エージェントによくある間違い: Action Text のコンテンツにスタイルを設定しないことです。生の出力はスタイルが適用されていない状態で表示されます。常に .trix-content または .lexxy-content の CSS を用意してください。

ステップ 7:添付ファイルを処理する

Action Text は、ファイル添付(エディターにドラッグ&ドロップされた画像やファイル)に Active Storage を使用します。

前提条件:

  • Active Storage がインストールされていること(bin/rails active_storage:install)
  • 画像処理用の libvips または ImageMagick
  • Gemfile に image_processing gem が含まれていること

添付ファイルのレンダリングをカスタマイズする:

<%# app/views/active_storage/blobs/_blob.html.erb %>
<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
  <% if blob.representable? %>
    <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [800, 600] : [1024, 768]) %>
  <% end %>

  <figcaption class="attachment__caption">
    <% if caption = blob.try(:caption) %>
      <%= caption %>
    <% else %>
      <span class="attachment__name"><%= blob.filename %></span>
      <span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
    <% end %>
  </figcaption>
</figure>
ステップ 8:N+1 クエリを避ける

これは非常に重要です。 リッチテキストを読み込むと、レコードごとに追加のクエリが発生します。必ず事前読み込みしてください。

# BAD — N+1 queries (one query per article for rich text)
Article.all.each { |a| a.content.to_s }

# GOOD — Preload rich text
Article.all.with_rich_text_content

# GOOD — Preload rich text AND its embedded attachments
Article.all.with_rich_text_content_and_embeds

スコープ名は動的です。 has_rich_text :name の宣言に基づいて、with_rich_text_#{name} と with_rich_text_#{name}_and_embeds になります。

# If you have: has_rich_text :body
Article.with_rich_text_body
Article.with_rich_text_body_and_embeds

# If you have: has_rich_text :description
Product.with_rich_text_description_and_embeds

エージェントによくあるミス: _and_embeds を忘れることです。これがないと、レンダリング時に添付画像の追加クエリが発生します。

ステップ 9:カスタム添付可能オブジェクト(埋め込み)

Signed GlobalID を使って、Active Record モデルをリッチテキスト内に埋め込めます。

# app/models/user.rb
class User < ApplicationRecord
  include ActionText::Attachable

  def to_attachable_partial_path
    "users/mention"
  end
end
<%# app/views/users/_mention.html.erb %>
<span class="user-mention">@<%= user.name %></span>

プログラムから挿入する場合:

user = User.find(1)
html = %(<action-text-attachment sgid="#{user.attachable_sgid}"></action-text-attachment>)
article.update!(content: "Hello #{html}")

レコードが削除されても適切に処理する:

class User < ApplicationRecord
  include ActionText::Attachable

  def self.to_missing_attachable_partial_path
    "users/missing_mention"
  end
end
<%# app/views/users/missing_mention.html.erb %>
<span class="user-mention user-mention--deleted">@deleted user</span>
ステップ 10:リッチテキストのテスト

モデルテスト:

require "test_helper"

class ArticleTest < ActiveSupport::TestCase
  test "accepts rich text content" do
    article = Article.new(title: "Test", content: "<h1>Hello</h1><p>World</p>")
    assert article.content.present?
    assert_includes article.content.to_plain_text, "Hello"
    assert_includes article.content.to_plain_text, "World"
  end

  test "content is blank when not set" do
    article = Article.new(title: "Test")
    assert article.content.blank?
  end
end

リクエストテスト:

require "test_helper"

class ArticlesRequestTest < ActionDispatch::IntegrationTest
  test "creates article with rich text" do
    assert_difference "Article.count", 1 do
      post articles_path, params: {
        article: { title: "Test", content: "<p>Rich text body</p>" }
      }
    end
    assert_equal "Rich text body", Article.last.content.to_plain_text
  end
end

システムテスト(エディター操作用):

require "application_system_test_case"

class ArticlesSystemTest < ApplicationSystemTestCase
  test "creates article with rich text editor" do
    visit new_article_path
    fill_in "Title", with: "My Article"

    # Fill the rich text editor
    find("trix-editor").click
    find("trix-editor").set("Hello from the editor")

    click_on "Create Article"
    assert_text "Hello from the editor"
  end
end
UUID 主キー

モデルで UUID を使用している場合は、Action Text のマイグレーションを更新してください。

# In the generated migration
t.references :record, null: false, polymorphic: true, index: false, type: :uuid
コンテンツのセキュリティ

Action Text は、許可リスト方式を使ってレンダリング時に HTML をサニタイズします。許可されたタグと属性のみが通過します。手動で sanitize を呼び出す必要はありません。

カスタムサニタイズ(必要な場合):

# config/application.rb
config.action_text.sanitizer_allowed_tags = ActionText::ContentHelper::ALLOWED_TAGS + ["iframe"]
config.action_text.sanitizer_allowed_attributes = ActionText::ContentHelper::ALLOWED_ATTRIBUTES + ["src", "frameborder"]

⚠️ 許可リストを拡張する際は、十分に注意してください。 <iframe> や <script> を追加すると、XSS の脆弱性につながります。すべてのコンテンツ作成者を信頼できる場合に限り、追加してください。

クイックリファレンス

基本的なコマンド
bin/rails action_text:install      # Install Action Text
bin/rails db:migrate               # Run migrations
bin/rails active_storage:install   # Install Active Storage (if not present)
モデルの宣言
has_rich_text :content                    # Single rich text field
has_rich_text :body                       # Name it whatever you want
has_rich_text :content                    # Multiple fields OK
has_rich_text :summary                    #   on the same model
フォームヘルパー
<%= form.rich_text_area :content %>
<%= form.rich_text_area :content, placeholder: "Write something..." %>
<%= form.rich_text_area :content, data: { controller: "editor" } %>
レンダリング
<%= @article.content %>                           # Safe HTML
<%= @article.content.to_plain_text %>             # Plain text
<%= truncate(@article.content.to_plain_text, length: 200) %>  # Excerpt
事前読み込み(N+1 の防止)
Model.with_rich_text_fieldname                    # Preload text only
Model.with_rich_text_fieldname_and_embeds         # Preload text + attachments
主要なファイル
ファイル目的
app/views/layouts/action_text/contents/_content.html.erbコンテンツのラッパー(.trix-content または .lexxy-content)
app/views/active_storage/blobs/_blob.html.erb添付ファイルのレンダリングテンプレート
app/assets/stylesheets/actiontext.cssAction Text のデフォルトスタイル

避けるべきアンチパターン

  1. インストールジェネレーターを実行しない — action_text:install を実行せずに has_rich_text を使うと、テーブル、JS、パーシャルが不足します
  2. コンテンツのスタイル設定をしない — Action Text の出力には、.trix-content/.lexxy-content 用の CSS が必要です
  3. N+1 を無視する — リストビューでは必ず with_rich_text_X_and_embeds を使ってください
  4. モデルにカラムを追加する — リッチテキストはモデルのテーブルではなく、action_text_rich_texts に保存されます
  5. 手動で HTML をサニタイズする — Action Text が処理するため、二重にサニタイズするとコンテンツが壊れます
  6. Lexxy のスタイルシートの順序を間違える — Lexxy の CSS はアプリの CSS より前に読み込む必要があります
  7. Active Storage の依存関係が不足している — libvips がないと、画像のレンダリングが壊れます
  8. 添付可能オブジェクトが見つからない場合の処理をしない — to_missing_attachable_partial_path がないと、削除されたレコードは空のボックスとして表示されます

コメント