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.1 ms ) SELECT sqlite_version ( * )
↳ app / controllers / posts_controller . rb : 17 :in 'show'
Post Load ( 0.1 ms ) SELECT "posts" . * FROM "posts" WHERE "posts" . "id" = ? LIMIT ? [[ "id" , 1 ], [ "LIMIT" , 1 ]]
↳ app / controllers / posts_controller . rb : 17 : in 'show'
ActionText :: RichText Load ( 0.2 ms ) 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.1 ms ) 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.1 ms ) 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.3 ms ) 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.