Se non ti dispiace sporcarti le mani con un po' di SQL puoi usare un funzione finestra per portare a termine il lavoro. Puoi ottenere gli ID post con questo SQL:
select id
from (
select id,
rank() over (partition by thread_id order by created_at desc)
from posts
where receiver_id = #{user.id}
) as dt
where rank = 1
Se vuoi più colonne aggiungile ad entrambe le clausole SELECT. Il #{user.id}
è, ovviamente, il destinatario che ti interessa.
La parte interessante è la funzione finestra:
rank() over (partition by thread_id order by created_at desc)
Questo partiziona la tabella in gruppi basati su thread_id
(una specie di GROUP BY localizzato), ordinali per timestamp (prima i più recenti) e poi rank()
restituisce 1 per la prima voce di ogni gruppo, 2 per la seconda, ecc.
Data una tabella simile a questa:
=> select * from posts;
id | receiver_id | thread_id | created_at
----+-------------+-----------+---------------------
1 | 1 | 2 | 2011-01-01 00:00:00
2 | 1 | 2 | 2011-02-01 00:00:00
3 | 1 | 2 | 2011-03-01 00:00:00
4 | 1 | 3 | 2011-01-01 00:00:00
5 | 1 | 4 | 2011-01-01 00:00:00
6 | 1 | 3 | 2011-01-01 13:00:00
7 | 2 | 11 | 2011-06-06 11:23:42
(7 rows)
La query interna ti dà questo:
=> select id, rank() over (partition by thread_id order by created_at desc)
from posts
where receiver_id = 1;
id | rank
----+------
3 | 1
2 | 2
1 | 3
6 | 1
4 | 2
5 | 1
(6 rows)
E poi avvolgiamo la query esterna attorno a quella per rimuovere solo le corrispondenze di alto livello:
=> select id
from (
select id,
rank() over (partition by thread_id order by created_at desc)
from posts
where receiver_id = 1
) as dt
where rank = 1;
id
----
3
6
5
(3 rows)
Quindi aggiungi le colonne extra che desideri e avvolgi il tutto in un Post.find_by_sql
e il gioco è fatto.