7

Rails 7 adds `#with_all_rich_text` to eager load all rich text associations at o...

 3 years ago
source link: https://blog.saeloun.com/2021/03/23/rails-adds-support-for-eager-loading-all-rich-text-associations-at-once
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Rails 7 added support for eager loading all rich text associations at once. We can now use #with_all_rich_text instead of eager loading each rich text association separately with #with_rich_text_{field_name}.

Before

We could eager load rich text associations using the helpers provided by ActionText.

Let’s take an example of the Post model. We have 3 rich text fields in the Post model - :summary, :body and :tldr.

class Post < ApplicationRecord
  has_rich_text :summary
  has_rich_text :body
  has_rich_text :tldr
end

Now, if we have to eager load all the rich_text associations we would end up doing the following:

def show
  Post
    .with_rich_text_summary
    .with_rich_text_body
    .with_rich_text_tldr
    .find(params[:id])
end

This will still make 4 queries to the database.

  • 1 query to load the Post and
  • 3 queries to the ActionText table to load the 3 rich_texts associations.

We can see the same in the log below.

Started GET "/posts/1" for ::1 at 2021-03-17 15:25:43 +0530
Processing by PostsController#show as HTML
  Parameters: {"id"=>"1"}
   (0.1ms)  SELECT sqlite_version(*)
  ↳ app/controllers/posts_controller.rb:17:in 'show'
  Post Load (0.1ms)  SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/controllers/posts_controller.rb:17:in 'show'
  ActionText::RichText Load (0.2ms)  SELECT "action_text_rich_texts".* FROM "action_text_rich_texts" WHERE "action_text_rich_texts"."record_type" = ? AND "action_text_rich_texts"."name" = ? AND "action_text_rich_texts"."record_id" = ?  [["record_type", "Post"], ["name", "summary"], ["record_id", 1]]
  ↳ app/controllers/posts_controller.rb:17:in 'show'
  ActionText::RichText Load (0.1ms)  SELECT "action_text_rich_texts".* FROM "action_text_rich_texts" WHERE "action_text_rich_texts"."record_type" = ? AND "action_text_rich_texts"."name" = ? AND "action_text_rich_texts"."record_id" = ?  [["record_type", "Post"], ["name", "body"], ["record_id", 1]]
  ↳ app/controllers/posts_controller.rb:17:in 'show'
  ActionText::RichText Load (0.1ms)  SELECT "action_text_rich_texts".* FROM "action_text_rich_texts" WHERE "action_text_rich_texts"."record_type" = ? AND "action_text_rich_texts"."name" = ? AND "action_text_rich_texts"."record_id" = ?  [["record_type", "Post"], ["name", "tldr"], ["record_id", 1]]

After

The new method #with_all_rich_text allows to load all the rich text associations at once.

With the same Post model with three action_text associations.

class Post < ApplicationRecord
  has_rich_text :summary
  has_rich_text :body
  has_rich_text :tldr
end

We will now use the newly introduced method #with_all_rich_text.

def show
  Post
    .with_all_rich_text
    .find(params[:id])
end

The above method will now only fire one query to the database. This is how the query will be generated.

Started GET "/posts/1" for ::1 at 2021-03-17 15:31:53 +0530
Processing by PostsController#show as HTML
  Parameters: {"id"=>"1"}
  SQL (0.3ms)  SELECT "posts"."id" AS t0_r0, "posts"."title" AS t0_r1, "posts"."body" AS t0_r2, "posts"."created_at" AS t0_r3, "posts"."updated_at" AS t0_r4, "posts"."status" AS t0_r5, "posts"."category" AS t0_r6, "action_text_rich_texts"."id" AS t1_r0, "action_text_rich_texts"."name" AS t1_r1, "action_text_rich_texts"."body" AS t1_r2, "action_text_rich_texts"."record_type" AS t1_r3, "action_text_rich_texts"."record_id" AS t1_r4, "action_text_rich_texts"."created_at" AS t1_r5, "action_text_rich_texts"."updated_at" AS t1_r6, "rich_text_bodies_posts"."id" AS t2_r0, "rich_text_bodies_posts"."name" AS t2_r1, "rich_text_bodies_posts"."body" AS t2_r2, "rich_text_bodies_posts"."record_type" AS t2_r3, "rich_text_bodies_posts"."record_id" AS t2_r4, "rich_text_bodies_posts"."created_at" AS t2_r5, "rich_text_bodies_posts"."updated_at" AS t2_r6, "rich_text_tldrs_posts"."id" AS t3_r0, "rich_text_tldrs_posts"."name" AS t3_r1, "rich_text_tldrs_posts"."body" AS t3_r2, "rich_text_tldrs_posts"."record_type" AS t3_r3, "rich_text_tldrs_posts"."record_id" AS t3_r4, "rich_text_tldrs_posts"."created_at" AS t3_r5, "rich_text_tldrs_posts"."updated_at" AS t3_r6 FROM "posts" LEFT OUTER JOIN "action_text_rich_texts" ON "action_text_rich_texts"."record_type" = ? AND "action_text_rich_texts"."name" = ? AND "action_text_rich_texts"."record_id" = "posts"."id" LEFT OUTER JOIN "action_text_rich_texts" "rich_text_bodies_posts" ON "rich_text_bodies_posts"."record_type" = ? AND "rich_text_bodies_posts"."name" = ? AND "rich_text_bodies_posts"."record_id" = "posts"."id" LEFT OUTER JOIN "action_text_rich_texts" "rich_text_tldrs_posts" ON "rich_text_tldrs_posts"."record_type" = ? AND "rich_text_tldrs_posts"."name" = ? AND "rich_text_tldrs_posts"."record_id" = "posts"."id" WHERE "posts"."id" = ? LIMIT ?  [["record_type", "Post"], ["name", "summary"], ["record_type", "Post"], ["name", "body"], ["record_type", "Post"], ["name", "tldr"], ["id", 1], ["LIMIT", 1]]

How this works?

The following method is responsible for finding all the rich_text associations for the model.

private

def rich_text_association_names
  reflect_on_all_associations(:has_one).collect(&:name).select { |n| n.start_with?("rich_text_") }
end

It is collecting all the has_one associations for the model starting with rich_text_ and eager loading all the associations at once.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK