Визуализируйте отношения JSON со многими ко многим в Phoenix Framework

У меня есть следующие модели

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

Для многих ко многим отношениям. Мой контроллер, чтобы показать это

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

  render(conn, "index.json", users: users)
end

По мнению у меня есть

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
 }

Когда я отправил запрос GET, я получил эту ошибку

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

Я знаю, что это может быть потому, что user.roles Мне нужно каким-то образом отформатировать, чтобы декодировать JSON, но я понятия не имею об этом. Я попробовал в форме

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

Но не работает.

Каков наилучший способ отобразить многие отношения?

1 ответ

Решение

Использование render_many/4 является правильным.

Если вы хотите определить функцию рендеринга "role.json" в том же модуле, вы можете сделать:

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

Обратите внимание, что мы проходим as: :role в функцию render_many. Это потому, что присваивает (%{role: role}) часть выведена из названия вида. В этом случае это UserView так было бы %{user: user} по умолчанию.

Если вы определите RoleView модуль, то вы можете просто переместить def render("role.json") функция к вашему новому RoleView и позвонить render_many без as опция:

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

Другой вариант, который может быть предпочтительным для вас, - это создать протокол в вашей модели:

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

Лично я чувствую, что это связывает вашу модель с вашей точки зрения, поэтому я предпочитаю использовать первый вариант.

Другие вопросы по тегам