Reputation: 2486
I have a table users
:
id first_name
--------------
1 Bill
2 Denise
who read multiple books
:
id book user_id read_at
---------------------------------------------------
1 Garry Potter 1 2020-1-1
2 Lord of the wrist watch 2 2020-1-1
3 90 Shades of navy 2 2020-1-2
I want to create a scope in my book
model that gets me the latest book for each user. There's plenty examples of doing this with pure SQL, the problem I'm running to is creating a flexible scope that can be used with a count, inner query or any other way you would typically use a scope.
So far I have this in my book
model:
def self.most_recent
inner_query = select('DISTINCT ON (user_id) *').order(:user_id, read_at: :desc)
select('*').from(inner_query, :inner_query).order('inner_query.id')
end
Which is very close to what I want. It works with a count however not in a more complicated situation.
For example if I want to get a list of users where their latest book is "Garry Potter", I try something like this:
User.where(id: Book.most_recent.where(book: 'Garry Potter').select(:user_id))
Active record gets confused and generates this SQL:
SELECT "users".* FROM "users" WHERE "users"."id" IN (SELECT "user_id", * FROM (SELECT "books"."user_id", DISTINCT ON (user_id) * FROM "books" ORDER BY "books"."user_id" ASC, "books"."read_at" DESC) inner_query WHERE "books"."book" = "Garry Potter" ORDER BY inner_query.id)
Which gives the following error:
ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: syntax error at or near "DISTINCT"
Is there an elegant way to achieve this?
Upvotes: 4
Views: 67
Reputation: 341
You can try change the method most_recent
by returning a query with where
:
def self.most_recent
# Select only the ids of the most recent books
inner_query = select('DISTINCT ON (user_id) books.id').order(:user_id, read_at: :desc)
# Return a where query like Book.where(id: <ids in the result set of the query above>)
where(id: inner_query)
end
# You should now be able to perform the following query
User.where(
id: Book.most_recent.where(book: 'Garry Potter').select(:user_id)
)
Upvotes: 1