Skip to content

Instantly share code, notes, and snippets.

@austbot
Forked from ericktai/1_user.rb
Last active August 23, 2017 07:26
Show Gist options
  • Save austbot/6d1a201e47538ff72189036c5929b398 to your computer and use it in GitHub Desktop.
Save austbot/6d1a201e47538ff72189036c5929b398 to your computer and use it in GitHub Desktop.
class User < ApplicationRecord
has_many :posts
has_many :comments
# id :integer not null, primary key
# name :string(50) default("")
end
class Post < ApplicationRecord
belongs_to :user
has_many :comments
# id :integer not null, primary key
# user_id :integer
# content :text default("")
# created_at :datetime
end
class Comment < ApplicationRecord
belongs_to :user
belongs_to :post
# id :integer not null, primary key
# user_id :integer
# post_id :integer
# content :text default("")
# created_at :datetime
end
class NewsfeedController < ApplicationController
# JSON endpoint that returns an array of Post objects in order of
# newest first, to oldest last. Each Post contains a User object
# (the author of the Post), and an array of Comments. Each Comment
# will also include the User object of the Comment's author.
# TODO: Newsfeed endpoint here
def index
# ** is to ensure the attribute serializer allows the nested relationships in the json like user <-> comment
render json: recent, include: '**', status: '200 OK'
end
def posts
# We use include to avoid n+1 queries
@post = Post
.includes(:user)
.includes(comments: [:user])
end
def recent
posts.order(created_at: :desc).limit(50)
end
end
[
{
"type": "Post",
"content": "First post",
"user": {
"type": "User",
"name": "Luke"
},
"comments": [
{
"type": "Comment",
"user": {
"type": "User",
"name": "Leia"
},
"content": "First comment"
},
{
"type": "Comment",
"user": {
"type": "User",
"name": "Han"
},
"content": "Second comment"
},
]
},
{
"type": "Post",
"content": "Second post",
"user": {
"type": "User",
"name": "Darth Vader"
},
"comments": [
{
"type": "Comment",
"user": {
"type": "User",
"name": "Boba Fett"
},
"content": "Third comment"
},
{
"type": "Comment",
"user": {
"type": "User",
"name": "Jabba"
},
"content": "Fourth comment"
},
]
}
]
@austbot
Copy link
Author

austbot commented Aug 23, 2017

@ericktai

Can you make the queries efficient?

By using includes, we avoid the back and forth chatter from app to sql server by loading the ids we need from the one model and piping them into the next query.
The generated sql is

  Post Load (0.6ms)  SELECT  `posts`.* FROM `posts` ORDER BY `posts`.`created_at` DESC LIMIT 50
  User Load (0.6ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` IN (...)
  Comment Load (2.4ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`post_id` IN (...)
  User Load (1.2ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` IN (...)

What if we want to reuse the query in other tasks? Would you organize the code in a different way?

Instead of putting all this in the controller I would factor it out to the model by using scopes or model methods that way other parts of the app can use it. After all Don't Repeat Yourself :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment