How are you approaching rendeding ecto model association using "viewless" JSON views?

Hey everyone! It’s been a while since I last built a Phoenix app, and I noticed that Phoenix 1.7 no longer supports Phoenix views. I know I can always add Phoenix views as a dependency, but I’m trying to do things the new way. Unfortunately, I’m having a hard time finding resources for building JSON-APIs that work with newer Phoenix versions. The JSON and APIs documentation only covers simple scenarios.

So, I’m curious to know how you’re organizing your JSON views when it comes to reusing them for rendering associations like when we used to have render_one and render_many helper functions to cross-reference them. For example, let’s say we have a post with many comments. How are you reusing the rendering logic for both the /posts and /comments resources, where the post also includes its comments but rendering them referencing the CommentJSON view or something?

Are you doing something like this or extracting code that translates Ecto schemas into Elixir maps and reusing it?

defmodule HelloWeb.PostJSON do
  @doc """
  Renders a single post.
  """
  def show(%{post: post}) do
    %{data: data(post)}
  end

  defp data(%Post{} = post) do
    %{
      id: post.id,
      title: post.title,
      comments: HelloWeb.CommentJSON.index(%{comments: post.comments})[:data]
    }
  end
end

defmodule HelloWeb.CommentJSON do
  def index(%{comments: comments}) do
    %{data: for(comment <- comments, do: comment_json(comment))}
  end

  defp comment_json(%Comment{} = comment) do
    %{
      id: comment.id,
      text: comment.type
    }
  end
end

Just use functional programming, like in your example. In my opinion, Ecto schemas have no business in the controller.

1 Like

Hi @krasenyp, thanks for your reply!

Regarding reusing serialization logic from the CommentJSON module in the PostJSON, are you referring to the module directly? Doing comments: HelloWeb.CommentJSON.index(%{comments: post.comments})[:data] felt a bit strange because almost sounding like I should have it extracted elsewhere and reused across JSON modules like this:

defmodule HelloWeb.PostJSON do
  def show(%{post: post}) do
    %{data: HelloWeb.Serializers.Post.json(post)}
  end
end

defmodule HelloWeb.CommentJSON do
  def index(%{comments: comments}) do
    %{data: for(comment <- comments, do: HelloWeb.Serializers.Comment.json(post)(comment))}
  end
end

Maybe I’m overcomplicating things. CommentJSON.comment_json/1 and PostJSON.data/1 (just realized it’s not called post_json) could be exposed as public functions that associated modules could call, like HelloWeb.CommentJSON.comment_json(comment).