© Brady Somerville, Adam Gamble, Cloves Carneiro Jr and Rida Al Barazi 2020
B. Somerville et al.Beginning Rails 6https://doi.org/10.1007/978-1-4842-5716-6_11

11. Action Text

Brady Somerville1 , Adam Gamble2, Cloves Carneiro Jr.3 and Rida Al Barazi4
(1)
Bowling Green, KY, USA
(2)
Gardendale, AL, USA
(3)
Hollywood, FL, USA
(4)
FONTHILL, ON, Canada
 

With version 6, Rails introduced a system to make it easy to enhance text areas with WYSIWYG (What You See Is What You Get) editors. Often, developers need to allow users—who may not be HTML-savvy—to edit HTML content. Rails developers have previously needed to choose from a variety of JavaScript-based WYSIWYG editors, integrate the assets into the Asset Pipeline, and connect them with the desired text area inputs. While Rails developers still have the freedom to do this if we desire, Action Text gives us first-class option.

In this chapter, we’ll cover the steps necessary to allow the users in our blog application to easily use HTML when editing their articles’ body fields.

Installation

To add support for using Action Text, we’ll need to do a few things. First, Action Text stores its data in a separate table (similar to Active Storage, as we saw in the previous chapter). Thankfully, Rails gives us a simple way to generate the needed database migrations. Next, we’ll need to include Action Text’s JavaScript and CSS in our application.

The first step is to run the action_text:install command, as shown in the following with the output:
> rails action_text:install
Copying actiontext.scss to app/assets/stylesheets
      create  app/assets/stylesheets/actiontext.scss
Copying fixtures to test/fixtures/action_text/rich_texts.yml
      create  test/fixtures/action_text/rich_texts.yml
Copying blob rendering partial to app/views/active_storage/blobs/_blob.html.erb
      create  app/views/active_storage/blobs/_blob.html.erb
Installing JavaScript dependencies
         run  yarn add trix@^1.0.0 @rails/actiontext@^6.0.2-1 from "."
yarn add v1.21.0
[1/4]   Resolving packages...
[2/4]   Fetching packages...
[3/4]   Linking dependencies...
[4/4]   Building fresh packages...
success Saved lockfile.
success Saved 2 new dependencies.
info Direct dependencies
├─ @rails/[email protected]
info All dependencies
├─ @rails/[email protected]
✨  Done in 4.81s.
Adding trix to app/javascript/packs/application.js
      append  app/javascript/packs/application.js
Adding @rails/actiontext to app/javascript/packs/application.js
      append  app/javascript/packs/application.js
Copied migration 20200304234710_create_action_text_tables.action_text.rb from action_text
Your output may differ slightly, but take note of some of the things this simple command did:
  • Added Action Text’s CSS to our app

  • Added Action Text’s JavaScript dependencies to our package.json and yarn.lock files

  • Installed Action Text’s JavaScript dependencies into our node_modules directory

  • Added Action Text’s JavaScript to our app’s JavaScript pack

  • Created the database migration we need to store Action Text data

The only thing left for us to do is to run our database migrations:
> rails db:migrate
== 20200304234710 CreateActionTextTables: migrating ===============
-- create_table(:action_text_rich_texts)
   -> 0.0048s
== 20200304234710 CreateActionTextTables: migrated (0.0049s) =======

Next we’ll describe each of the changes that just happened in a little more detail. We could just skip ahead to enhancing a blog, but we can learn a little bit by paying closer attention to these details.

Action Text CSS

The action_text:install command we ran magically added Action Text’s CSS to our application, so that the Trix editor (Action Text’s WYSIWYG editor of choice) has the styles it needs to look good.

But how did it do this? It simply added the file app/assets/stylesheets/actiontext.css. Don’t we need to explicitly load that style sheet somewhere? By default, we don’t need to. Listing 11-1 shows an excerpt from our main style sheet.
/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
 * vendor/assets/stylesheets directory can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the bottom of the
 * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
 * files in this directory. Styles in this file should be added after the last require_* statement.
 * It is generally better to create a new file per style scope.
 *
 *= require_tree .
 *= require_self
 */
...
Listing 11-1

Excerpt from app/assets/stylesheets/application.csshttps://gist.github.com/nicedawg/92ed0bf488b5b4aee1eef85982f8383a

The comments are helpful here, so read them closely. Essentially, they’re saying you could add a special comment line like /*= require ‘shiny’ */ and Rails will look for a CSS file named “shiny.scss” or “shiny.css” in a few of your application’s directories. If it finds one, it will include it in your application.css file. If it doesn’t find one in your app’s directories, it will then start looking in your included gems for a style sheet with the given name.

This is good to know, but it doesn’t answer our question—how did Action Text’s CSS actually get added to our application.css? The answer is in the bolded line—the “= require_tree .directive in our comment says “look for any style sheets in this directory and any subdirectories and include them.” Since the action_text:install command added app/assets/stylesheets/actiontext.scss, the require_tree directive included that style sheet automatically.

This is a great example of how Rails again uses convention over configuration to simplify the process of adding a style sheet. However, there may be times when require_tree is undesirable. Perhaps you need to control the order of style sheet inclusion, or maybe you need to exclude certain style sheets from being included. In those cases, you may need to use the require directive to explicitly list the style sheets you wish to include. We won’t need to do that for our application, but it’s good to know this exists. For more information on these directives (like require_tree and require), see https://guides.rubyonrails.org/asset_pipeline.html#manifest-files-and-directives.

Let’s take a quick look at the actiontext.scss file which our action_text:install command added in Listing 11-2.
//
// Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and
// the trix-editor content (whether displayed or under editing). Feel free to incorporate this
// inclusion directly in any other asset bundle and remove this file.
//
//= require trix/dist/trix
// We need to override trix.css’s image gallery styles to accommodate the
// <action-text-attachment> element we wrap around attachments. Otherwise,
// images in galleries will be squished by the max-width: 33%; rule.
.trix-content {
  .attachment-gallery {
    > action-text-attachment,
    > .attachment {
      flex: 1 0 33%;
      padding: 0 0.5em;
      max-width: 33%;
    }
    &.attachment-gallery--2,
    &.attachment-gallery--4 {
      > action-text-attachment,
      > .attachment {
        flex-basis: 50%;
        max-width: 50%;
      }
    }
  }
  action-text-attachment {
    .attachment {
      padding: 0 !important;
      max-width: 100% !important;
    }
  }
}
Listing 11-2

app/assets/stylesheets/actiontext.scsshttps://gist.github.com/nicedawg/aa815191362c98adb5e445c4e803ea82

This style sheet is included in our main style sheet. Notice the bolded text. This style sheet includes another style sheet—trix/dist/trix. Where does that come from? We didn’t notice that getting added to our application. The require directive also loads from our node_modules directory. If you look in node_modules/trix/dist, you’ll see a trix.css file. Apparently, Trix’s default CSS needed just a bit of tweaking to work with Action Text, so our actiontext.scss adds some overrides.

Action Text JavaScript

We just learned how Action Text integrated its CSS into our app; now, we’ll take a look at how Action Text integrated its JavaScript into our app.

In the output of action_text:install, we see that it ran the command “yarn add trix@^1.0.0 @rails/actiontext@^6.0.2-1 from "."” Several things happened here:
  • An entry was added to package.json which listed trix as a JavaScript dependency, requiring the version number to begin with 1.

  • An entry was added to package.json which listed @rails/actiontext as a JavaScript dependency, requiring the version number to begin with 6.

  • The latest versions of those JavaScript libraries (which satisfied the version requirements) were downloaded into our app’s node_modules directory.

  • The exact versions of these two JavaScript libraries (and their dependencies) were added to yarn.lock, to ensure that future installations of these libraries for our app will receive the exact same versions.

Then, the action_text:install command appended a couple of lines to our app/javascript/packs/application.js file, as shown in Listing 11-3.
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
// Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
// or the `imagePath` JavaScript helper below.
//
// const images = require.context('../images', true)
// const imagePath = (name) => images(name, true)
require("trix")
require("@rails/actiontext")
Listing 11-3

app/javascripts/packs/application.jshttps://gist.github.com/nicedawg/b647350cce50f921b2e8ab666474b841

Similar to how Action Text’s CSS was included in our application.css, the bolded require lines include Action Text’s JavaScript in our main application.js pack. We can use require in our JavaScript files to include other JavaScript files from our own application (e.g., app/javascript/shiny.js) or to include JavaScript files from our app’s dependencies found in our node_modules directory. (The latter is where our trix and @rails/actiontext JavaScript dependencies are found.)

Action Text Database Storage

The last task our action_text:install command performed was to generate a migration to store our Action Text data. After running the migration, we see the following table added to our db/schema.rb, as shown in Listing 11-4.
...
  create_table "action_text_rich_texts", force: :cascade do |t|
    t.string "name", null: false
    t.text "body"
    t.string "record_type", null: false
    t.integer "record_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true
  end
...
Listing 11-4

Action Text Table Added to db/schema.rbhttps://gist.github.com/nicedawg/b4d79afe726e026afae59821a3b70385

Similar to the active_storage_attachments table from the previous chapter’s work, the action_text_rich_texts table is a polymorphic table, meaning it can belong to many different types of Active Record models by using the record_type column to store the class name of the model a particular action_text_rich_texts row belongs to, along with the record_id column to identify which particular record (of type record_type) it belongs to.

For illustration, consider the sample data which might appear in the action_text_rich_texts database table in Table 11-1.
Table 11-1

Sample Data in the action_text_rich_texts Database Table

id

name

body

record_type

record_id

1

body

<p>Hey!</p>

Article

1

2

body

<p>Yo</p>

FAQ

1

3

excerpt

<p>Hi</p>

Article

1

4

body

<p>:-)</p>

Article

2

In this sample data, we see that
  • Article 1 has a body of “<p>Hey!</p>” and an excerpt of “<p>Hi</p>”.

  • FAQ 1 has a body of “<p>Yo</p>”.

  • Article 2 has a body of “<p>:-)</p>”, but no excerpt.

As you can see, this structure is very flexible. It can hold the content for any attribute of any model. This usually is much more convenient than needing to define separate database storage each model’s rich text needs.

Using Action Text in Our Blog

Now that we’ve investigated how Action Text adds its CSS, JavaScript, and database storage needs to our app, we can enhance our blog with greater understanding of what’s happening behind the scenes. In the following steps, we will allow our users to create articles with HTML bodies.

Updating the Article Model

First, we need to make our Article model aware that we want to use Action Text for its body attribute.

In Listing 11-5, we declare in our Article model that we want to use Action Text to handle its body attribute.
class Article < ApplicationRecord
  validates :title, :body, presence: true
  belongs_to :user
  has_and_belongs_to_many :categories
  has_many :comments
  has_one_attached :cover_image
  attr_accessor :remove_cover_image
  after_save { cover_image.purge if remove_cover_image == '1' }
  has_rich_text :body
  scope :published, -> { where.not(published_at: nil) }
  scope :draft, -> { where(published_at: nil) }
  scope :recent, -> { where('articles.published_at > ?', 1.week.ago.to_date) }
  scope :where_title, -> (term) { where("articles.title LIKE ?", "%#{term}%") }
  def long_title
    "#{title} - #{published_at}"
  end
  def published?
    published_at.present?
  end
  def owned_by?(owner)
    return false unless owner.is_a?(User)
    user == owner
  end
end
Listing 11-5

Adding Action Text to Article#bodyhttps://gist.github.com/nicedawg/98424877354b24411de2dbe5d2a1fa79

By adding has_rich_text :body, a few things happened behind the scenes. Now, Article#body is a has_one relation which returns the relevant ActionText::RichText object from the action_text_rich_texts table, rather than returning the value of the body column from the articles table. Also, assigning to the body (e.g., article.body = “<p>Hey!</p>”) now assigns the given value to the body attribute of the ActionText::RichText related object, rather than the article’s old body attribute. Lastly, adding has_rich_text :body added a couple of scopes to our class to make it easier to include the related ActionText::RichText objects, helping us avoid N+1 queries. We’ll demonstrate this later in the chapter.

Migrating Our Data

You may have noticed in the previous section that one side effect of using has_rich_content :body is that the value is retrieved from (and stored in) a different table. If we were starting from scratch, no problem. However, we have some data in our articles table’s body column which is now being ignored! To preserve our data (and for illustration), we’ll migrate our body data from the articles table to the action_text_rich_texts table. Then we’ll add a migration to remove the body column from the articles table; since we no longer need it, we should remove it to prevent possible confusion down the road.

First, let’s generate a migration to copy our body data from the articles table to the action_text_rich_texts table. Run the following command to generate the migration file:
> rails g migration MigrateArticleBodyToActionText
Next, modify the generated migration file to look like Listing 11-6. (Note: In my case, that’s db/migrate/20200305025834_migrate_article_body_to_action_text.rb, but your timestamp will differ.)
class MigrateArticleBodyToActionText < ActiveRecord::Migration[6.0]
  def up
    execute <<-SQL
      INSERT INTO action_text_rich_texts (
        name,
        body,
        record_type,
        record_id,
        created_at,
        updated_at
      ) SELECT
        'body' AS name,
        body,
        "Article",
        id,
        created_at,
        updated_at
      FROM articles
    SQL
  end
  def down
    execute <<-SQL
      DELETE FROM action_text_rich_texts
    SQL
  end
end
Listing 11-6

Migrating Article Body Data to ActionText::RichTexthttps://gist.github.com/nicedawg/5654a9de5ac781d71462912af754d659

Let’s talk about this migration. First, notice we defined separate up and down methods in this migration; we need to execute some custom SQL, so we need to declare what should happen in each direction.

When migrating up, we added some SQL to create action_text_rich_texts records using our articles data. We used the INSERT INTO .. SELECT... syntax which most SQL databases understand. It may look complicated, but it’s essentially saying “for each record in the articles table, create a record in the action_text_rich_texts by mapping these values to those.”

Let’s run the migration and then query the database directly to see how our data looks:
> rails db:migrate
> rails dbconsole
sqlite> .headers on
sqlite> .mode column
sqlite> SELECT * FROM articles;
sqlite> SELECT * FROM action_text_rich_texts;
sqlite> .exit

Let’s explain those sqlite commands. “.headers on” adds headers to the display of output in the SQLite console. “.mode column” formats the output to be in a column layout. We took the time to configure these options to make the next two steps easier to read. Then, we get the contents of the articles and action_text_rich_texts tables. You should see the same number of records in each, and you should see the action_text_rich_texts table populated with data that matches the article record to which it corresponds.

Now that we are confident the data looks okay, we can try it out in rails console :
> rails console
irb(main):001:0> Article.first.body
   (0.4ms)  SELECT sqlite_version(*)
  Article Load (0.2ms)  SELECT "articles".* FROM "articles" ORDER BY "articles"."id" ASC LIMIT ?  [["LIMIT", 1]]
  ActionText::RichText Load (0.2ms)  SELECT "action_text_rich_texts".* FROM "action_text_rich_texts" WHERE "action_text_rich_texts"."record_id" = ? AND "action_text_rich_texts"."record_type" = ? AND "action_text_rich_texts"."name" = ? LIMIT ?  [["record_id", 1], ["record_type", "Article"], ["name", "body"], ["LIMIT", 1]]
  Rendered /Users/brady.somerville/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/actiontext-6.0.2.1/app/views/action_text/content/_layout.html.erb (Duration: 1.8ms | Allocations: 478)
=> #<ActionText::RichText id: 9, name: "body", body: #<ActionText::Content "<div class="trix-conte...">, record_type: "Article", record_id: 1, created_at: "2020-02-25 02:23:56", updated_at: "2020-02-29 15:41:47">

As the console output shows, Article.first.body did a few things. First, it loaded the Article object. Then, it loaded its corresponding ActionText::RichText object for the body attribute. Then it loaded an action_text template! Lastly, it returned the body as an instance of the ActionText::RichText class. Notice the body value of the ActionText::RichText object—it’s actually an instance of the ActionText::Content class. We won’t dig even further at this point, but just know this means our simple Article#body attribute now has a lot of new behavior attached to it.

Now that we know our Article model is fetching its body content from Action Text’s database tables, let’s add a database migration to remove the body column from the articles table. This isn’t necessary, but since we aren’t using it (and we’re sure we don’t need its data anymore), we’ll remove it to keep things tidy.

Run the following Rails command to generate another migration:
> rails g migration RemoveBodyFromArticles body:text
By following the naming convention and adding the name and data type of the column we wish to remove, Rails generates exactly the migration we need, as seen in Listing 11-7.
class RemoveBodyFromArticles < ActiveRecord::Migration[6.0]
  def change
    remove_column :articles, :body, :text
  end
end
Listing 11-7

Migration to Remove the Body Column from the Articles Tablehttps://gist.github.com/nicedawg/176b87f36550f8269fa4afcf73f4502e

This migration will remove the body column from the articles table when migrating upward and will add the column back when calling rails db:rollback. However, when rolling back, it won’t restore the data. We’re okay with that, because we know we don’t need it, but be aware that extra steps would be necessary if that were not the case.

Run rails db:migrate to actually remove the unneeded column from articles:
> rails db:migrate

At this point, we feel confident that our Article model is correctly integrated with Action Text, so let’s continue enhancing our blog application.

Updating the Article View

After having updated our Article model to use Action Text for its body, if you were try to load the root path (or /articles path) of our application, you would see an error: “NoMethodError in Articles#index: undefined method `strip' for #<ActionText::RichText:0x00007ff6f6b51478>”. We shouldn’t be too surprised; we changed what type of object article.body returns, so we have to deal with it a little differently.

We were using simple_format in the article partial template to safely allow links (and other basic HTML) in the display of our article bodies. Modify your app/views/articles/_article.html.erb file to match Listing 11-8.
<div class="article">
  <h3>
    <%= link_to article.title, article %>
    <% if article.owned_by? current_user %>
      <span class="actions">
        <%= link_to 'Edit', edit_article_path(article) %>
        <%= link_to 'Delete', article, confirm: 'Are you sure?', method: :delete %>
      </span>
    <% end %>
  </h3>
  <hr>
  <% if article.cover_image.attached? %>
    <%= image_tag article.cover_image.variant(resize_to_limit: local_assigns.fetch(:cover_image_options, [200, 200])) %>
    <hr>
  <% end %>
  <%= article.body %>
</div>
Listing 11-8

Displaying Action Text Content in _article.html.erbhttps://gist.github.com/nicedawg/2c3d1d2f14899ffb944099879935857c

After updating app/views/articles/_article.html.erb, we can now load the various pages that display an article body without error. Nothing really looks different than before, but that’s good! Our article bodies don’t have any HTML in them. Yet.

Updating the Article Form

Now, almost everything’s in place. The support system for articles having HTML in their body fields is there; we just need to update the form to use a WYSIWYG editor.

Again, Action Text makes this easy. Edit your app/views/articles/_form.html.erb to match Listing 11-9.
<%= form_with(model: article, local: true) do |form| %>
  <% if article.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(article.errors.count, "error") %> prohibited this article from being saved:</h2>
      <ul>
        <% article.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>
  <div class="field">
    <%= form.label :title %>
    <%= form.text_field :title %>
  </div>
  <div class="field">
    <%= form.label :cover_image %>
    <%= form.file_field :cover_image %>
    <% if article.cover_image.attached? %>
      <p>
        <%= image_tag article.cover_image.variant(resize_to_limit: [50, 50]) %>
        <br>
        <%= form.label :remove_cover_image do %>
          <%= form.check_box :remove_cover_image %> Remove this image
        <% end %>
      </p>
    <% end %>
  </div>
  <div class="field">
    <%= form.label :location %>
    <%= form.text_field :location %>
  </div>
  <div class="field">
    <%= form.collection_check_boxes(:category_ids, Category.all, :id, :name) do |b| %>
      <% b.label { b.check_box + b.text } %>
    <% end %>
  </div>
  <div class="field">
    <%= form.label :excerpt %>
    <%= form.text_field :excerpt %>
  </div>
  <div class="field">
    <%= form.label :body %>
    <%= form.rich_text_area :body %>
  </div>
  <div class="field">
    <%= form.label :published_at %>
    <%= form.datetime_select :published_at %>
  </div>
  <div class="actions">
    <%= submit_or_cancel(form) %>
  </div>
<% end %>
Listing 11-9

Updating the Article Form to Use the Trix Editor for Its Body Input https://gist.github.com/nicedawg/d76b2b3ad4e4b1dd5eefd2af2f1a5a3f

As you can see, all we had to do was replace the text_area form helper we were using for the body attribute with the rich_text_area form helper which Action Text provides. Edit an article, and see our WYSIWYG editor for the body tag in action! Your article form should look something like Figure 11-1.
../images/314622_4_En_11_Chapter/314622_4_En_11_Fig1_HTML.jpg
Figure 11-1

Article form using Trix as WYSIWYG editor for the body field

Try it out! Restart your Rails server to be sure all of our new code is loaded, and use the article form to add content to your body, using Trix’s toolbar to make some of your content bold and add links, lists, and other formatting. Then view the article, and see your fancy formatting in the body tag.

Cleaning Up N+1 Queries

In an earlier section in this chapter, we described what happened when we added has_rich_text :body to our Article model. One of the benefits we described was the inclusion of scopes to help us deal with N+1 queries.

Why is this necessary? While viewing your rails server output, load the root path (which renders each article in your database), and notice how many SQL queries are executed. See Listing 11-10 for an example.
Started GET "/" for ::1 at 2020-03-04 23:04:50 -0600
Processing by ArticlesController#index as HTML
  Rendering articles/index.html.erb within layouts/application
  Article Load (0.2ms)  SELECT "articles".* FROM "articles"
  ↳ app/views/articles/index.html.erb:4
  User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  ↳ app/controllers/application_controller.rb:6:in `current_user'
  CACHE User Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  ↳ app/models/article.rb:29:in `owned_by?'
  ActiveStorage::Attachment Load (0.2ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments"...
  ↳ app/views/articles/_article.html.erb:12
  ActionText::RichText Load (0.2ms)  SELECT "action_text_rich_texts".* FROM "action_text_rich_texts"...
  ↳ app/views/articles/_article.html.erb:16
  User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/models/article.rb:29:in `owned_by?'
  ActiveStorage::Attachment Load (0.3ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments"...
  ↳ app/views/articles/_article.html.erb:12
  ActionText::RichText Load (0.3ms)  SELECT "action_text_rich_texts".* FROM "action_text_rich_texts"...
  ↳ app/views/articles/_article.html.erb:16
  CACHE User Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/models/article.rb:29:in `owned_by?'
  ActiveStorage::Attachment Load (0.2ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments"...
  ↳ app/views/articles/_article.html.erb:12
  ActionText::RichText Load (0.2ms)  SELECT "action_text_rich_texts".* FROM "action_text_rich_texts"...
  ↳ app/views/articles/_article.html.erb:16
  ActiveStorage::Attachment Load (0.2ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments"...
  ↳ app/views/articles/_article.html.erb:12
  ActionText::RichText Load (0.2ms)  SELECT "action_text_rich_texts".* FROM "action_text_rich_texts"...
  ↳ app/views/articles/_article.html.erb:16
  Rendered collection of articles/_article.html.erb [4 times] (Duration: 48.2ms | Allocations: 13120)
  Rendered articles/index.html.erb within layouts/application (Duration: 49.9ms | Allocations: 13897)
[Webpacker] Everything's up-to-date. Nothing to do
Completed 200 OK in 68ms (Views: 64.7ms | ActiveRecord: 2.6ms | Allocations: 19139)
Listing 11-10

Too Many SQL Queries for Loading Articleshttps://gist.github.com/nicedawg/70ba78c747b96fa0ec4d7a924b760586

To help make the output easier to read, we truncated and omitted extremely long lines. But scanning the output, we see a pattern; over and over again, we query the same three tables: users, active_storage_attachments, and action_text_rich_texts.

At this point, we only have a few articles, so the performance hit of making at least three queries may not be noticeable. But each article in our database would result in at least three queries being executed, so imagine if we had 50 articles or 100. This doesn’t scale, so we need to deal with these N+1 queries we’ve accumulated along the way.

To combat these N+1 queries in our articles index, we just need to make a simple change to our ArticlesController. Modify your app/controllers/articles_controller.rb so it resembles Listing 11-11.
class ArticlesController < ApplicationController
  before_action :authenticate, except: [:index, :show]
  before_action :set_article, only: [:show, :edit, :update, :destroy]
  # GET /articles
  # GET /articles.json
  def index
    @articles = Article.includes(:user).with_rich_text_body.with_attached_cover_image.all
  end
  # rest of code omitted for brevity
end
Listing 11-11

Fixing N+1 Queries in ArticlesControllerhttps://gist.github.com/nicedawg/e47509b6a42e7732475c0db89bf40b65

Instead of simply calling Article.all, we add a few things to eliminate our N+1 queries. First, we use .includes(:user) to hint to Active Record that we want to know the user which each article belongs to. (:user is the name of the relevant association, so that’s what we provide to includes. Next, we chain the with_rich_text_body scope. This scope was added automatically to our Article class when we added has_rich_content :body and similarly hints to Rails we want to efficiently load the relevant action_text_rich_texts records for each article. Lastly, we chain the with_attached_cover_image scope, which Active Storage automatically added to our class when we added has_one_attached :cover_image in the previous chapter.

Now that we’ve addressed these N+1 queries, watch the rails server output again while you load the root path. It should resemble Listing 11-12.
Started GET "/" for ::1 at 2020-03-04 23:27:15 -0600
Processing by ArticlesController#index as HTML
  Rendering articles/index.html.erb within layouts/application
  Article Load (0.2ms)  SELECT "articles".* FROM "articles"
  ↳ app/views/articles/index.html.erb:4
  User Load (0.4ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (?, ?)  [["id", 2], ["id", 1]]
  ↳ app/views/articles/index.html.erb:4
  ActionText::RichText Load (0.4ms)  SELECT "action_text_rich_texts".* FROM "action_text_rich_texts"...
  ↳ app/views/articles/index.html.erb:4
  ActiveStorage::Attachment Load (0.3ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments"...
  ↳ app/views/articles/index.html.erb:4
  User Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  ↳ app/controllers/application_controller.rb:6:in `current_user'
  Rendered collection of articles/_article.html.erb [4 times] (Duration: 12.5ms | Allocations: 3421)
  Rendered articles/index.html.erb within layouts/application (Duration: 24.7ms | Allocations: 7684)
[Webpacker] Everything's up-to-date. Nothing to do
Completed 200 OK in 36ms (Views: 32.9ms | ActiveRecord: 1.5ms | Allocations: 13062)
Listing 11-12

No More N+1 Queries When Loading Articleshttps://gist.github.com/nicedawg/cfe9422f14763cbf831a224d7c74b8c5

Again, note that we truncated and omitted some extremely long lines for clarity. But look again for the SELECT statements. Instead of dozens of SELECT statements, there are only a few! This is a good sign that our optimizations were effective.

Summary

In this chapter, we ran the action_text:install command and investigated what changes it made to our app in order to support using Trix as a WYSIWYG editor. Then, we enhanced our blog application by allowing users to create HTML in their articles’ bodies without needing to learn HTML.

While we ended up covering most of what you need to know to work with Action Text, https://edgeguides.rubyonrails.org/action_text_overview.html is a great resource for future reference.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.132.214