How to render multiple relations using opts include

Hi,
I am new to Elixir Phoenix and in the leaning process.

I am trying to implement the apis and have defined multiple schemas with belongs_to, has_one and has_many relations. Following is my controller function.

def show(conn, %{“id” => id}) do
state = GeoData.get_state!(id)
|> Repo.preload([:country, :cities])

# render(conn, "show.json-api", data: state, opts: [include: "cities, country"])
render(conn, "show.json-api", data: state, opts: [include: "country, cities"])

end

I want the GET on http://localhost:4000/api/states to return both country and cities info. However, this renders only cities or country, depending on the order in which I specify them in the render call above. How do I get the API to return both the relations or even more relations as applicable. Whats the syntax to render multiple such relations? Btw, I am able to successfully preload them before rendering and I verified the same with IO.inspect. Its just the rendering part thats failing.

Appreciate any help in this regard.

Thanks,
Rajasekhar.

Hello and welcome…

Then You should show your view show.json-api

Maybe also add this to your pipeline, and tell us what the state is

state = GeoData.get_state!(id)
|> Repo.preload([:country, :cities])
|> IO.inspect()

BTW I would not use that name for a view, I would prefer show-api.json… just to not mess with json type.

Hi, thanks for your response.

Here is the the output of IO.inspect. As we can see both cities and country are preloaded.

%MyApp.GeoData.State{
  __meta__: #Ecto.Schema.Metadata<:loaded, "states">,
  cities: [
    %MyApp.GeoData.City{
      __meta__: #Ecto.Schema.Metadata<:loaded, "cities">,
      id: 1,
      inserted_at: ~N[2020-07-15 15:31:45],
      is_hidden: false,
      name: "Lake Jaunita",
      state: #Ecto.Association.NotLoaded<association :state is not loaded>,
      state_id: 1,
      updated_at: ~N[2020-07-15 15:31:45]
    }
  ],
  country: %MyApp.GeoData.Country{
    __meta__: #Ecto.Schema.Metadata<:loaded, "countries">,
    id: 1,
    inserted_at: ~N[2020-07-15 15:31:45],
    is_hidden: false,
    name: "India",
    states: #Ecto.Association.NotLoaded<association :states is not loaded>,
    updated_at: ~N[2020-07-15 15:31:45]
  },
  country_id: 1,
  id: 1,
  inserted_at: ~N[2020-07-15 15:31:45],
  is_hidden: false,
  name: "South Dashawn",
  updated_at: ~N[2020-07-15 15:31:45]
}

And, my_app_web/views/state_view.ex file is as follows.

defmodule MyAppWeb.StateView do
  use MyAppWeb, :view
  use JaSerializer.PhoenixView
  attributes([:name, :is_hidden])
  has_one :country,
    serializer: MyAppWeb.CountryView,
    include: true,
    identifiers: :when_included
  has_many :cities,
    serializer: MyAppWeb.CityView,
    include: true,
    identifiers: :when_included
end

Regarding show.json-api, following was the recommendation given by Phoenix at the time of compilation after I switched to JaSerializer.

warning: Please use show.json-api instead. This will stop working in a future version.
warning: Passing data via `:model`, `:states` or `:state`
          atoms to JaSerializer.PhoenixView has be deprecated. Please use
          `:data` instead. This will stop working in a future version.

Please use code fence ``` to delimite your code… I edited your post to add them.

The problem is with the use of JaSerializer, but I cannot help You because I have never used it.

At least, your data seems fine :slight_smile:

@kokolegorille Thank you, appreciate your help so far.

Anybody else, who can help me to resolve this please? How to render data from multiple relationships in API?

Happy to share the issue is resolved. The issue is very trivial.

Referring to the documentation for includes option here(https://github.com/vt-elixir/ja_serializer),

Include

By specifying the include option, the serializer will only side-load the relationships specified. This option should be a comma separated list of relationships. Each relationship should be a dot separated path.
Example: include: "author,comments.author"

I somehow figured out that include options are comma separated, but there should not be any space in between. Once I removed the space between cities, and country, it started working.

Wrong stmt:  render(conn, "show.json-api", data: state, opts: [include: "cities, country"])
Correct stmt: render(conn, "show.json-api", data: state, opts: [include: "cities,country"])

It looks strange that white spaces are not handled and there is no explicit mention about this anywhere in the documentation. However, I am happy that issue is resolved.

Once again, thanks to @kokolegorille for your willingness to help.

1 Like

Here is what my API returns after the whitespace is removed in include options.
Data in json format that includes both cities and country info.

{
  "data": {
    "attributes": {
      "is-hidden": false,
      "name": "South Dashawn"
    },
    "id": "1",
    "relationships": {
      "cities": {
        "data": [
          {
            "id": "1",
            "type": "city"
          }
        ]
      },
      "country": {
        "data": {
          "id": "1",
          "type": "country"
        }
      }
    },
    "type": "state"
  },
  "included": [
    {
      "attributes": {
        "is-hidden": false,
        "name": "Lake Jaunita"
      },
      "id": "1",
      "type": "city"
    },
    {
      "attributes": {
        "is-hidden": false,
        "name": "India"
      },
      "id": "1",
      "type": "country"
    }
  ],
  "jsonapi": {
    "version": "1.0"
  }
}