ashkan117
Preloaded associations and views patterns
I’m wondering how do people structure their JSON Api’s with Phoenix. Using the blogs example, let’s say I have a blogs view like the following
def render("show.json", %{blog: blog}) do
%{
id: blog.id,
title: blog.title,
posts: render_many(blog.posts, PostView, "post.json")
}
This runs into things like what if posts has its own associations like comments, etc.
In my controller maybe sometimes I don’t care about the associations and other times I do. This leads me to two main problems?
- Are there any standards people follow to handle when a view should try and render associations?
- Should there be some helper function in Phoenix.View that handles conditionally rendering associations?
For the first problem I’m just curious how people do something like the following
def show(conn, %{"id" => id}) do
blog = Blogs.get_blog_with_all(id)
# assume no associations are preloaded
render(conn, "show.json", blog: blog)
end
def some_action(conn, %{"id" => id}) do
blog = Blogs.get_blog_with_all(id, preload: [:association_one, :association_two])
# here I want to include the two associations in my view
render(conn, "show_with_association_one_and_two.json", blog: blog)
end
Having a specific view for each combination of association seems tedious.
For the second problem I mean something like having a helper function maybe_render_association that will add the field to the view if the association is loaded or it will not include the field at all. My thinking is we don’t want to return nil in the case that the association is not loaded since it’s not actually nil. That pattern would look like
def render() do
def render("post.json", %{post: post}) do
%{
id: post.id,
title: post.title,
body: post.body,
comments: render_many(post.comments, CommentView, "comment.json")
}
# it would be nice if we can have it inside the above map but
# since it's a conditional field not sure how
|> maybe_render_association(post.updated_by, :updated_by, User, "basic.json")
end
end
Most Liked
LostKobrakai
I don’t think phoenix should drive such helpers. I’d argue a none preloaded association reaching a view template expecting that association to be preloaded is an error. There’s no definitive answer to if that association is missing because of a bug, because of the intention to skip the key of the association in the json or because of the intention to mask the data not being loaded with an empty value ([] or nil). I’ve seen people argue in favor of all of these, so a generalize solution is likely not the answer.
You can however quite build such a helper for your project and your intentions.
Personally I’d push this concern to other places though. E.g. have PostUser – like a user as shown in relation to posts – vs a UserDetails – user as rendered by e.g. /users/:id. That requires more upfront modeling, but makes code a lot more explicit. You’re not passing around data, which could fit 10+ branches of results – and you hope you get the one you intend – but you pass around data that matches the one or few intented usecases. It also makes change easier, because the approach of a single very dynamic view template works great if all “branches” are kind of supersets of the base case. It gets hairy though once they have conflicting keys. Say eventually you want to mask part of the users as returned with posts, but not mask that for user details. It might feel like duplication at first, but going that route is more maintainable in the long run.
Popular in Discussions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








