I have a classic nested comment schema where only one level of children is needed.
schema "comments" do
field(:message, :string)
field(:removed_at, :utc_datetime)
belongs_to(:post, Post)
belongs_to(:user, User)
belongs_to(:parent, Comment)
has_many(:children, Comment, foreign_key: :parent_id)
timestamps()
end
I’m working on the query to fetch the comments for a given post in a nested fashion. The query looks great, with the user data joined and only making two queries for the two levels of comments. (only preloading children at the top level)
children_query =
from c in Comment,
join: u in assoc(c, :user),
order_by: [desc: c.inserted_at],
preload: [user: u]
query =
from c in Comment,
join: u in assoc(c, :user),
preload: [user: u, children: ^children_query],
where: c.post_id == ^post_id,
where: is_nil(c.parent_id),
order_by: [desc: c.inserted_at]
Repo.all(query)
The structure being returned looks great and all the data I need is there. But now I would like to pare down that data by selecting only what I need.
The only way I could figure out how to do that is by mapping over the lists of comments and picking out the attributes. I can’t leave the comment structs to the jason encoder because I want the children for the top level comments but not for the nested comments. (so if I include :children
in the @derive {Jason.Encoder...
for comments it breaks on the nested comments because children are not preloaded)
So is there a way to select specific fields when dealing with nested, preloaded data?
The mapping and picking doesn’t seem like a performance issue but nested map seems barbaric after such an elegant job with the preload. Here is what I am currently doing:
Comments.get_by_post(post_id)
|> Enum.map(fn c ->
%{
children:
Enum.map(c.children, fn c ->
%{
id: c.id,
message: c.message,
inserted_at: c.inserted_at,
username: c.user.username
}
end),
id: c.id,
message: c.message,
inserted_at: c.inserted_at,
username: c.user.username
}
end)