Enumerable not implemented for when rendering

So I do not know where does this error originated. I know this error is repeated but please help me.

Successfully call a get user but after that gets a stacktrace below

[debug] QUERY OK source="users" db=0.3ms
SELECT u0."id", u0."email", u0."password_hash", u0."first_name", u0."last_name", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [10]
[debug] QUERY OK source="rooms" db=1.1ms decode=0.7ms queue=0.2ms
SELECT r0."id", r0."name", r0."topic", r0."inserted_at", r0."updated_at", u1."id" FROM "rooms" AS r0 INNER JOIN "users" AS u1 ON u1."id" = ANY($1) INNER JOIN "user_rooms" AS u2 ON u2."user_id" = u1."id" WHERE (u2."room_id" = r0."id") ORDER BY u1."id" [[10]]

So my stacktrace:

[error] #PID<0.527.0> running SlickWeb.Endpoint (cowboy_protocol) terminated
Server: localhost:4000 (http)
Request: GET /api/users/10/rooms
** (exit) an exception was raised:
    ** (Protocol.UndefinedError) protocol Enumerable not implemented for %Slick.Accounts.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, email: "j@safgjot.com", first_name: "jheccfrey", id: 10, inserted_at: ~N[2018-10-14 06:22:53.773384], last_name: "sajcdaot", password: nil, password_hash: "$argon2i$v=19$m=65536,t=6,p=1$6tZZ8+mIXdkUTX/0Jf6DlQ$8Ln8CdB5DmMZO+kChdeNniuFuNwGbAjWM1u/a2Mz/Ig", rooms: [], updated_at: ~N[2018-10-14 06:22:53.773397]}. This protocol is implemented for: DBConnection.PrepareStream, DBConnection.Stream, Date.Range, Ecto.Adapters.SQL.Stream, File.Stream, Function, GenEvent.Stream, HashDict, HashSet, IO.Stream, List, Map, MapSet, Postgrex.Stream, Range, Stream
        (elixir) /home/vagrant/2018-09-24_12-39-03/scripts/elixir/deb/elixir_1.7.3-1/lib/elixir/lib/enum.ex:1: Enumerable.impl_for!/1
        (elixir) /home/vagrant/2018-09-24_12-39-03/scripts/elixir/deb/elixir_1.7.3-1/lib/elixir/lib/enum.ex:141: Enumerable.reduce/3
        (elixir) lib/enum.ex:2979: Enum.map/2
        (slick) lib/slick_web/views/user_view.ex:27: SlickWeb.UserView.render/2
        (phoenix) lib/phoenix/view.ex:399: Phoenix.View.render_to_iodata/3
        (phoenix) lib/phoenix/controller.ex:729: Phoenix.Controller.__put_render__/5
        (phoenix) lib/phoenix/controller.ex:746: Phoenix.Controller.instrument_render_and_send/4
        (slick) lib/slick_web/controllers/user_controller.ex:1: SlickWeb.UserController.action/2
        (slick) lib/slick_web/controllers/user_controller.ex:1: SlickWeb.UserController.phoenix_controller_pipeline/2
        (slick) lib/slick_web/endpoint.ex:1: SlickWeb.Endpoint.instrument/4
        (phoenix) lib/phoenix/router.ex:275: Phoenix.Router.__call__/1
        (slick) lib/slick_web/endpoint.ex:1: SlickWeb.Endpoint.plug_builder_call/2
        (slick) lib/plug/debugger.ex:122: SlickWeb.Endpoint."call (overridable 3)"/2
        (slick) lib/slick_web/endpoint.ex:1: SlickWeb.Endpoint.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:16: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) /home/hei/Desktop/elixir/slick/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4

The function where the error points out:

def rooms(conn, _params) do
    current_user = Slick.Guardian.Plug.current_resource(conn)
    rooms = Slick.Accounts.get_user_rooms(current_user.id)

    render(conn, "rooms.json", %{rooms: rooms})
  end

UserView Module uses RoomView

defmodule SlickWeb.UserView do
  use SlickWeb, :view
  alias SlickWeb.UserView

 ...omitted
  def render("rooms.json", %{rooms: rooms}) do
    %{data: render_many(rooms, SlickWeb.RoomView, "room.json")}
  end
end

RoomView Module:

defmodule SlickWeb.RoomView do
  use SlickWeb, :view
  alias SlickWeb.RoomView

...omitted

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

So what I’m trying to do is preload the association of users and display the associated entity to json which will be consumed by a React client

The query below works on iex terminal and work as expected that returns a result

 def get_user_rooms(id) do
    User
    |> Slick.Repo.get!(id)
    |> Slick.Repo.preload(:rooms)
  end

My suspicion regarding this error would be the ViewModule part but I don’t really get it.

But what is the result exactly?

The error suggests that collection in
https://github.com/phoenixframework/phoenix/blob/v1.3.4/lib/phoenix/view.ex#L282

is actually a single user structure (which doesn’t support Enumerable) rather than as expected a list of room structures.

In fact as written get_user_rooms suggests that a single user structure is returned with the rooms preloaded - and then that user structure is assigned to rooms in rooms/2 - rather than extracted out of the user structure.

So try instead:

def get_user_rooms(id) do
    User
    |> Slick.Repo.get!(id)
    |> Slick.Repo.preload(:rooms)
    |> Map.get(:rooms, [])
  end

PS: You should really compose a separate “user rooms query” for this particular case - currently you are having Ecto issue two separate queries for no good reason. If you just want the rooms then just query the rooms. The intent behind preloading is to acquire the entire graph of data - when you actually need the entire graph of data. Here your are tossing the results from the user query away - so don’t retrieve it in the first place.

2 Likes

Thank you for the advice.

So I can’t directly query on the rooms because it doesn’t associate with users which is the id that the function accepts.

Users and Rooms are associated through third table with many_to_many relationship.

Ecto has already shown you that you can - reformatting from your post:

SELECT
  r0."id",
  r0."name",
  r0."topic",
  r0."inserted_at",
  r0."updated_at",
  u1."id"
FROM
  "rooms" AS r0
  INNER JOIN "users" AS u1 ON u1."id" = ANY($1)
  INNER JOIN "user_rooms" AS u2 ON u2."user_id" = u1."id"
WHERE
  (u2."room_id" = r0."id")
ORDER BY u1."id" [[10]]

or

SELECT
  rooms.*
FROM
  rooms
  INNER JOIN users ON users.id = :user_id
  INNER JOIN user_rooms ON user_rooms.user_id = users.id
WHERE
  user_rooms.room_id = rooms.id
ORDER BY
  users.id

I think it would be worth your time to go through something like http://www.postgresqltutorial.com/

There seems to be the notion that you can use Ecto without knowing SQL (in general) but my observation has been that not knowing SQL makes using Ecto effectively that much more difficult.

Ecto makes a lot more sense once you know SQL.

Programming Ecto (Beta 4) - Introduction, p. x

2 Likes

So I’m thinking of using Joins for this type of query, just that I though of utilizing Ecto.Repo for now.

Untested, my first attempt would be something like:

user_id = 10
query = from(rm in Room,[
  inner_join: ur in UserRoom, on: ur.room_id == rm.id,
  where: ur.user_id == ^user_id
])

Ecto.Query.from/2
Ecto.Query.join/5
Ecto.Query.where/3

Ecto.Query

1 Like

Got it to work. Make a custom query then pass it to Repo.all()

Just curious why write a custom query when you can achieved the same results with just preloading the association.

It always pays to keep the load on the database light. The explicit query only requires a single query while the preload requires additional queries. And using Ecto.Query.select/3 you can get exactly the data you need, mixing and matching from different tables without wasting any bandwidth on data you are just going to ignore anyway.

However there are times where you will want to utilize the fact that the database will cache results from previous queries (but that is a trickier topic).

Well, that’s a very useful information.

I will try to use Ecto.Query sometimes, which I grasped the basics of it. It lets the developer customized their query before calling Ecto.Repo functions.

Thank you very much for the responses. Very much appreciated.