Best way to include user-specific data for other records in a Phoenix view

This is a fairly simple repo structure question that I want to get right. Say I have 3 tables: users, posts, and bookmarks. A user can bookmark a post so on whole a post has_many bookmarks. Simple enough so far.

When I’m rendering the posts#index view I want to indiciate to a user whether a user has bookmarked a given post. Currently I query for all the posts and bookmarks like so:

# In controller
def index(conn, params) do
  posts = Posts.list_all_for_page(params["page"])
  bookmarks = Bookmarks.list_all_for_user_and_posts(current_user, posts)
end

# in view (pseudocode)
for post <- @posts do
  if Enum.find(bookmarks, nil, fn bookmark -> bookmark.post_id == post.id) do
    render saved_as_bookmark
  end
end

This isn’t exactly how it’s happening but it’s the same idea. This feels pretty incorrect to me and instead I’d like to have the bookmarking state as an attribute on the post so I can do something like if posts.bookmark_for_user ... instead. What’s the conventional way to accomplish this? I was thinking that a in addition to a post having_many bookmarks it could also has_one bookmark_for_user that gets loaded as part of the list_all_for_page query.

How about building a query that will preload the bookmark if the bookmark belongs to the user? Something lke:

Repo.all(
  from p in Posts,
  outer_join: b in Bookmarks,
  on: b.post_id == p.id and b.user_id == ^user.id
  # preloads...
)

Then in the view you’d check if the bookmark is not nil to see if the user has bookmarked the post.

Thank you for the help! I was thinking of doing something like this, but the issue I kept coming back to is that post.bookmarks would be a list type, whereas this new post.bookmark_for_user would be of type Bookmark | nil.

I’m trying to reconcile this by adding field :bookmark_for_user, Bookmark, virtual: true in the posts schema, but it looks like you can’t use Bookmark as a type in this case. Do you have any suggestions for how to represent this association within the schema itself?

Imo all the little “issues” you’re hitting here are a result of trying to make an improper abstraction work. The Post schema should be blissfully unaware of the fact that it’s going to be listed on a page you show to one special user and that the page wants to know if that one special user has bookmarked the post. At most the post should be aware of the fact that there are bookmarks pointing to users having placed the bookmarks.

If you really want to do post.is_bookmarked or something similar in the templates/view, then it’s the job of a more specialized model. You could create a additional (read only) schema and consolidate the data in the core of the application (akin to read models e.g. in event sourcing) or you could fetch the data like before in the controller, but transform it to a view model, which is only known to the view/web layer.