Details:
* Use UNION instead of UNION ALL if you have duplicates, or if you're not worried about performance when Postgres has to scan for duplicates.
* WITH Queries in Postgres
* Recursive queries can loop indefinitely if e.g. self.parent = self. If this happens, use the ANY() function to check if path has been visited once already. Alternatively, use validations to make sure circular references don't happen, and hope for the best.
* Use Arel's recursive feature to generate recursive CTE queries with Arel.

WITH latest_customer_transactions AS (
SELECT
*, rank() OVER (PARTITION BY user_id ORDER BY created_at desc) AS rank
FROM
transactions
)
SELECT
id, customer_id
FROM
latest_customer_transactions
WHERE
rank = 1;

Keep your fingers crossed that it works. For alternatives, see Shtack Overflow.

The query returns nothing, so let's do this instead:
* install the unaccent extension
* create an "immutable unaccent" function
* apply "unaccent" and "lower" to the query
* apply "unaccent" and "lower" to the index