Give back many, many JSON relationships in the Phoenix Framework

I have the following models

defmodule App.User do use App.Web, :model alias App.User schema "users" do field :name, :string has_many :roles_users, App.RolesUser has_many :roles, through: [:roles_users, :role] timestamps end end defmodule App.Role do use App.Web, :model schema "roles" do has_many :roles_users, App.RolesUser has_many :users, through: [:roles_users, :user] field :name, :string timestamps end end defmodule App.RolesUser do use App.Web, :model schema "roles_users" do belongs_to :role, App.Role belongs_to :user, App.User timestamps end end 

Is for many, many relationships. My controller to be shown

 def index(conn, _params) do users = Repo.all(User) |> Repo.preload(:roles) render(conn, "index.json", users: users) end 

In my view

 def render("index.json", %{users: users}) do %{users: render_many(users, App.UserView, "user.json")} end def render("show.json", %{user: user}) do %{user: render_one(user, App.UserView, "user.json")} end def render("user.json", %{user: user}) do %{id: user.id, name: user.name, roles: user.roles } 

When I sent a GET request, I got this error

 unable to encode value: {nil, "roles"} 

I know this may be due to the fact that user.roles needs to be formatted in some way to decode JSON, but I have no idea about it. I tried in shape

 def render("user.json", %{user: user}) do %{id: user.id, name: user.name, roles: render_many(roles, App.UserView, "roles.json") } 

But does not work.

What is the best way to map many to many relationships?

+6
source share
1 answer

Using render_many / 4 is correct.

If you want to define the role.json rendering function in the same module, you can do:

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

Note that we pass as: :role to the render_many function. This is because part of the assignment ( %{role: role} ) is inferred from the view name. In this case, this is a UserView , so the default is %{user: user} .

If you define a RoleView module, then you can simply move the def render("role.json") function def render("role.json") to the new RoleView and call render_many without the as option:

 ... roles: render_many(user.roles, MyApp.RoleView, "role.json") ... 

Another option that may be preferable for you is to get the protocol in your model:

 defmodule App.Role do use App.Web, :model @derive {Poison.Encoder, only: [:id, :name]} schema "roles" do has_many :roles_users, App.RolesUser has_many :users, through: [:roles_users, :user] field :name, :string timestamps end 

Personally, I feel that this connects your model with your look, so I prefer to use the first option.

+17
source

All Articles