I am trying to return a list of parent records, along with each parents most recent child record.
In my controller I have:
def index
projects = current_user.projects.includes(:tasks)
render json: projects.as_json(
methods: [:most_recent_task],
), status: 200
end
The method most_recent_task
uses an approach articulated here and summarized below:
class Task < ApplicationRecord
class << self
def in_order
order(created_at: :asc)
end
def recent(n)
in_order.endmost(n)
end
def endmost(n)
all.only(:order).from(all.reverse_order.limit(n), table_name)
end
end
end
and
class Project < ApplicationRecord
has_many :tasks
def most_recent_task
tasks.recent(1)[0]
end
end
This approach returns the correct JSON response, but I am now obviously dealing with n+1 queries for Task
on every request.
I've tried chaining scopes, using :includes
and :limit
, but can't seem to crack this nut. Perhaps using a JSON serializer could solve it? But I am trying to avoid that extra dependency for now. Any advice?
One solution is to define a has_one with an association scope:
has_one :most_recent_task, -> { order(created_at: :asc) }, class_name: "Task"
You can then use includes
to eagerly load the data:
>> Project.includes(:most_recent_task).all
Project Load (0.3ms) SELECT "projects".* FROM "projects" LIMIT $1 [["LIMIT", 11]]
Task Load (0.5ms) SELECT "tasks".* FROM "tasks" WHERE "tasks"."project_id" IN (1, 2) ORDER BY "tasks"."created_at" ASC
Note that it's querying all tasks for each project, not just the most recent one. But there's no N+1, and Project#most_recent_task
is nicely expressive.