Json view problem

I have a controller named MyAppWeb.ProfilesGamesController something like this

defmodule MyAppWeb.ProfilesGamesController do
  use MyAppWeb, :controller
 
  def index(conn, _params) do
    games = GameModel.list_games()
    render(conn, "index.json", games: games)
  end
end

I have view where I’m calling like this

defmodule MyAppWeb.ProfilesGamesView do
  use MyAppWeb, :view
  alias MyAppWeb.ProfilesGamesView

  def render("index.json", %{games: games}) do
    %{data: render_many(games, ProfilesGamesView, "index.json")}
  end

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

But when I try to call my api. I’m getting this error

** (exit) an exception was raised:
    ** (Phoenix.Template.UndefinedError) Could not render "index.json" for MyAppWeb.ProfilesGamesView, please define a matching clause for render/2 or define a template at "lib/myappweb_web/templates/profiles_games/*". No templates were compiled for this module.

You are using profiles_games, but You pass games…

One way to catch those errors is to use a catch all clause, and inspect params.

But You can specify what should be the name to use, with as parameter. Like this

%{data: render_many(games, ProfilesGamesView, "index.json", as: :game)}

Also, use this

alias __MODULE__

# instead of

alias MyAppWeb.ProfilesGamesView

And the terminology for one game should be game.json, not index.json, but this is just cosmetic.

Even if I remove profiles from there and pass only games. Then also I’m getting the same error. I changed my controller to GamesController but still

Add a catch all clause and show the parameters You have…

def render(action, params) do
  IO.inspect action
  IO.inspect params
end

I have resolved that. Also I changed that to this as you said

defmodule MyAppWeb.GamesView do
  use MyAppWeb, :view
  alias __MODULE__

  def render("index.json", %{games: games}) do
    %{data: render_many(games, GamesView, "game.json")}
  end

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

You’re saying like this for this?

%{data: render_many(games, ProfilesGamesView, "index.json", as: :game)}

If You changed the name of controller and view to match game, You don’t need as parameter.

If You prefer to keep ProfilesGamesView add as: :game.

And yes, this is how I would wrote it…

But if I use game.json like that. I’m getting this error


Request: GET /api/v1/profiles/games
** (exit) an exception was raised:
    ** (Phoenix.Template.UndefinedError) Could not render "game.json" for MyAppWeb.GamesView, please define a matching clause for render/2 or define a template at "lib/myapp_web/templates/games/*". No templates were compiled for this module.

Add the catch clause I mentionned, and show the parameters You get.

Yes I did that.

"index.json"
%{
  conn: %Plug.Conn{
    adapter: {Plug.Cowboy.Conn, :...},
    assigns: %{
      games: [
        %MyApp.Profiles.Games.Game{
          __meta__: #Ecto.Schema.Metadata<:loaded, "game">,
          id: 1,
          name: "siddhant",
          thumbnail_url: nil,
          twitch_id: nil
        }
      ],

The catch clause should be at the end of all clauses, not at the start. You want to see why game.json fail.

Nice. So this time I got this

"game.json"
%{
  games: %MyApp.Profiles.Games.Game{
    __meta__: #Ecto.Schema.Metadata<:loaded, "game">,
    id: 1,
    name: "siddhant",
    thumbnail_url: nil,
    twitch_id: nil
  },
  view_module: MyAppWeb.GamesView,
  view_template: "game.json"
}

So You see why… You expect game, but receive games.

And it’s because You don’t use normal naming. You use plural when it should be singular.

GameController
GameView

using as: :game would have solved this too.

3 Likes

You’re right actually. I changed all that to singular and it worked. Now i understand more about this.

Thanks for helping