Collaborative Wishlists
A wishlist in most stores is a private list of products. Here it is a collaborative, shareable object with followers, co-editors, gift purchases, and price-drop alerts — a small social layer on top of the catalog.
What a shopper can do
- Save products to one or more wishlists, with an optional target price.
- Share a wishlist publicly via a per-list token — no account needed to view it.
- Invite collaborators as editors or viewers.
- Follow someone else’s shared wishlist and get notified when they add items.
- Buy a gift from a wishlist; the purchase is hidden from the owner so the surprise holds.
- Set price alerts — get emailed when a saved item drops to a target price.
How sharing works
Sharing is implemented as a Wishlist::Sharable concern. Each wishlist carries
its own share_token, and the public view is served from an unauthenticated
route:
GET /shared_wishlist/:token # public, no login required
Every other wishlist route lives inside an authenticate :user block, so the
public surface is exactly one deliberate endpoint. The public page also emits
Open Graph meta tags, so a shared link unfurls nicely in chat apps and social
feeds.
Collaboration model
Three tables carry the social features:
| Table | Purpose |
|---|---|
wishlist_collaborators |
Roles — owner, editor, viewer — with invitation tokens |
wishlist_follows |
Follow a shared wishlist, get notified on new items |
gift_purchases |
A purchaser marks an item bought; it is hidden from the owner |
Authorization for each action runs through Pundit policies, so a viewer can never mutate a list they were only invited to read.
Price alerts & re-engagement
Wishlists are also a marketing surface, driven by background jobs:
- Price alerts —
wishlist_itemsstore atarget_price. When a product goes on sale,WishlistSaleNotificationJobchecks watched items and emails matches, recordingprice_alert_notified_atso a shopper is not spammed. - Restock alerts notify shoppers when an out-of-stock saved item returns.
- Abandoned-wishlist reminders — a daily job nudges shoppers who saved
items but never came back, tracked by
wishlist_last_visited_atandwishlist_reminder_sent_at.
Performance
Wishlist counts appear on every page, so they cannot trigger a query each time:
- Counter caches keep
wishlist_items_countcurrent on bothUserandWishlist. WishlistCacheServicecaches rendered fragments and invalidates them per user on any change viaWishlistCacheService.invalidate_for_user.- A live count is broadcast over Turbo Streams so the header badge updates without a refresh.
Key files
| Concern | Files |
|---|---|
| Models | Wishlist, WishlistItem, WishlistCollaborator, WishlistFollow, GiftPurchase |
| Services | WishlistService, WishlistSharingService, WishlistCollaborationService, WishlistCacheService, WishlistNotificationService |
| Jobs | WishlistSaleNotificationJob, AbandonedWishlistReminderJob |
| UI | app/components/wishlist/, WishlistMailer |