Outputting data from related tables in json view

Hi All,

I’m trying to figure out how I can show data from multiple tables in a single render function in an API only Phoenix 1.3 app.

I have a users schema and a documents schema where a user has_many documents and a document belongs_to a user.

The index action of my User Controller looks like

  def index(conn, _params) do
    users = Auth.list_users()
    render(conn, "index.json", users: users)
  end

I have an Auth context with a user below it so Auth.list_users looks like:

  def list_users do
    User
    |> Repo.all
    |> Repo.preload(:documents)
  end

If I make a GET request to /api/users I do see the following debug output which leads me to believe documents are being retrieved.

[debug] Processing with MyAppWeb.UserController.index/2
Parameters: %{}
Pipelines: [:api]
[debug] QUERY OK source=“users” db=2.3ms
SELECT u0.“id”, u0.“email”, u0.“password”, u0.“name”, u0.“inserted_at”, u0.“updated_at” FROM “users” AS u0 []
[debug] QUERY OK source=“documents” db=2.9ms
SELECT d0.“id”, d0.“file_content_type”, d0.“file_file_name”, d0.“file_file_size”, d0.“file_updated_at”, d0.“intialization_vector”, d0.“member_readable”, d0.“name”, d0.“user_id”, d0.“inserted_at”, d0.“updated_at”, d0.“user_id” FROM “documents” AS d0 WHERE (d0.“user_id” = $1) ORDER BY d0.“user_id” [1]

in lib/my_app_web views/user_view.ex I have

  def render("index.json", %{users: users}) do
    %{data: render_many(users, UserView, "user.json")}
  end

  def render("user.json", %{user: user}) do
    %{id: user.id,
      email: user.email,
      name: user.name
    }
  end

That renders the json correctly but I’m not sure how I can get documents to be listed as part of that as well.

I would like the json response to be something like:

%{id: user.id,
    email: user.email,
    name: user.name
    documents: [
     {id: document.id,
      document_name: document.name
     }
    ]}

I thought maybe referencing user.documents might work but it doesn’t and I’m still incredibly new to Phoenix, Elixir and Functional Programming as a whole so I’m a little stumped.

Any insight as to how I can accomplish this or what I’m missing would be greatly appreciated.

Thank you!

You might get it like this maybe…

  def render("user.json", %{user: user}) do
    %{id: user.id,
      email: user.email,
      name: user.name,
      documents: Enum.map(user.documents, &render_doc(&1))
    }
  end

  def render_doc(doc) do
    %{id: doc.id, document_name: doc.name}
  end

Beware of typo…

What You want is (% in front)

    %{id: document.id,
      document_name: document.name
     }

Thanks @kokolegorille, that got me in the right direction. What I ended up doing to get it to work is by adding to the initial render function

  def render("user.json", %{user: user}) do
    %{id: user.id,
     email: user.email,
     name: user.name,
     documents: render_many(user.documents, __MODULE__, "document.json", as: :document)
   }
 end

Then adding in a second render function for the document:

   def render("document.json", %{document: document}) do
    %{id: document.id
    }
  end
1 Like