Rails 7 adds disable_joins: true option to has_one :through association
source link: https://blog.saeloun.com/2021/06/01/rails-add-disable-join-option-to-has-one-association
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.
Rails 7 adds disable_joins: true option to has_one :through association
Jun 1, 2021 , by Santanu Karmakar 4 minute readIn our previous
post,
we’ve shown how to use the disable_joins
option for has_many: :through
association.
In this post, we would like to show, how that works for the has_one: :through
association as well.
Let’s consider the use case where:
- Users can create multiple posts.
- Anyone looking at the post can add a comment - crowd-sourced comments.
In this case, we can store the crowd-sourced comments in a separate database, as comments can grow quickly and may need a different kind of data management approach.
The database.yml
can be like below:
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: root
password: password
development:
primary:
<<: *default
database: rails_demo_development
crowd_sourced:
<<: *default
migrations_paths: db/migrate_crowd_sourced
database: rails_demo_crowd_sourced_development
The models would look like below:
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :primary, reading: :primary }
end
# app/models/crowd_sourced_record.rb
class CrowdSourcedRecord < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :crowd_sourced, reading: :crowd_sourced }
end
# app/models/user.rb
class User < ApplicationRecord
has_many :posts
has_one :latest_post, -> { order('posts.created_at desc').limit(1) }, class_name: 'Post'
end
# app/models/user.rb
class Post < ApplicationRecord
belongs_to :user
has_many :comments
has_one :latest_comment, -> { order('comments.created_at desc').limit(1) }
end
# app/models/comment.rb
class Comment < CrowdSourcedRecord
end
Before
In Rails 6, to fetch the latest comment for a user, we need to add a custom method, as associations can’t join across databases.
# app/models/user.rb
class User < ApplicationRecord
has_many :posts
has_one :latest_post, -> { order('posts.created_at desc').limit(1) }, class_name: 'Post'
def latest_comment
Comment.order('comments.created_at desc').where(post_id: posts.pluck(:id)).limit(1)
end
end
Here, we are fetching the most recent comment created on the posts created by the user.
As comments are in different a database, need to pluck
the post ids.
> User.first.latest_comment
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]]
(0.3ms) SELECT "posts"."id" FROM "posts" WHERE "posts"."user_id" = $1 [["user_id", 1]]
Comment Load (0.6ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN ($1, $2) /* loading for inspect */ ORDER BY comments.created_at desc LIMIT $3 [[nil, 1], [nil, 11], ["LIMIT", 1]]
=> #<ActiveRecord::Relation [#<Comment id: 51, post_id: 11, name: "Lesa Boyer", email: "[email protected]", comment: "Bad song!", created_at: "2021-05-25 10:15:12.527229000 +0000", updated_at: "2021-05-25 10:15:12.527229000 +0000">]>
For all such associations across different databases, we will have to add custom methods as above.
After
Rails 7 has
added
an option in has_one :through
association to make it easier to have multiple databases joining.
We can use the has_one :through
association with the newly added disable_joins: true
option like below.
We do need to add one association in the User
model for the latest_post
in this case.
class User < ApplicationRecord
has_many :posts
has_many :comments, through: :posts, disable_joins: true
has_one :latest_post, -> { order('posts.created_at desc').limit(1) }, class_name: 'Post'
has_one :latest_comment, through: :latest_post, disable_joins: true
end
class Post < ApplicationRecord
belongs_to :user
has_many :comments
has_one :latest_comment, -> { order('comments.created_at desc').limit(1) }, class_name: 'Comment'
end
If we try to load the latest_comment
association now, it fires 2 separate queries to load the post ids,
and the latest comment with those post ids.
> User.first.latest_comment
User Load (0.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]]
Post Pluck (0.6ms) SELECT "posts"."id" FROM "posts" WHERE "posts"."user_id" = $1 ORDER BY posts.created_at desc [["user_id", 1]]
Comment Load (0.6ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN ($1, $2) ORDER BY comments.created_at desc LIMIT $3 [["post_id", 11], ["post_id", 1], ["LIMIT", 1]]
=> #<Comment id: 51, post_id: 11, name: "Lesa Boyer", email: "[email protected]", comment: "Bad song!", created_at: "2021-05-25 10:15:12.527229000 +0000", updated_at: "2021-05-25 10:15:12.527229000 +0000">
This allows us to eager load the latest comment.
> User.includes(:latest_comment)
User Load (0.3ms) SELECT "users".* FROM "users" /* loading for inspect */ LIMIT $1 [["LIMIT", 11]]
Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ORDER BY posts.created_at desc LIMIT $11 [["user_id", 1], ["user_id", 2], ["user_id", 3], ["user_id", 4], ["user_id", 5], ["user_id", 6], ["user_id", 7], ["user_id", 8], ["user_id", 9], ["user_id", 10], ["LIMIT", 1]]
Comment Load (0.3ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY comments.created_at desc LIMIT $2 [["post_id", 11], ["LIMIT", 1]]
=> #<ActiveRecord::Relation [#<User id: 1, name: "Milton Dicki", email: "[email protected]", created_at: "2021-04-20 17:13:20.492541000 +0000", updated_at: "2021-04-20 17:13:20.492541000 +0000">, #<User id: 2, name: "Crystle Davis", email: "[email protected]", created_at: "2021-04-20 17:13:20.529307000 +0000", updated_at: "2021-04-20 17:13:20.529307000 +0000">, #<User id: 3, name: "Junita Sauer", email: "[email protected]", created_at: "2021-04-20 17:13:20.554967000 +0000", updated_at: "2021-04-20 17:13:20.554967000 +0000">, ...>]>
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK