Problem fixing/querying: Protocol Ecto.Queryable not implemented

I am trying to select a specific lesson from a table and load its associated exercises in a template with something like:

<%= lesson.exercise %>

these are the schemas:

schema "lessons" do
    field :lesson_id, :string
    field :url, :string
    field :title, :string
    field :description, :string
    has_many :exercises, Swish.Exercise
schema "exercises" do
    field :nativeword, :string
    field :foreignword, :string
    belongs_to :lessons, Swish.Lesson, foreign_key: :lesson_id

in the IEX i ran this query:
Repo.all from l in Lesson, join: e in assoc(l, :exercises), preload: [exercises: e]

which returned:

QUERY OK source="lessons" db=22.6ms decode=5.6ms
SELECT l0."id", l0."lesson_id", l0."url", l0."title", l0."description", l0."inserted_at", l0."updated_at", e1."id", e1."nativeword", e1."foreignword", e1."lesson_id", e1."inserted_at", e1."updated_at" FROM "lessons" AS l0 INNER JOIN "exercises" AS e1 ON e1."lesson_id" = l0."id" []
[%Swish.Lesson{__meta__: #Ecto.Schema.Metadata<:loaded, "lessons">,
  description: "The road to hell is paved with good intentions",
  exercises: [%Swish.Exercise{__meta__: #Ecto.Schema.Metadata<:loaded, "exercises">,
    foreignword: "Hello", id: 1, inserted_at: ~N[2017-07-23 02:21:47.078325],
    lesson: #Ecto.Association.NotLoaded<association :lesson is not loaded>,
    lesson_id: 3, nativeword: "hola",
    updated_at: ~N[2017-07-23 02:21:47.085057]},

This at first looked alright to me but now i see that lesson association is not correct. I got this error:
protocol Ecto.Queryable not implemented for [%Swish.Lesson{__meta__: #Ecto.Schema.Metadata<:loaded, "lessons">, description: "The road to hell is paved with good intentions", exercises: [%Swish.Exercise{__meta__: #Ecto.Schema.Metadata<:loaded, "exercises">, foreignword: "Hello", id: 1, inserted_at: ~N[2017-07-23 02:21:47.078325], lesson_id: 3, lessons: #Ecto.Association.NotLoaded<association :lessons is not loaded>, nativeword: "hola", updated_at: ~N[2017-07-23 02:21:47.085057]},
when running these functions and queries in a phoenix framework:

plug :load_exercises when action in [:show]

  defp load_exercises(conn, _) do
    query =
      Exercise
      |> Lesson.preload_exercises
      exercises = Repo.all query
      assign(conn, :exercises, exercises)
  end

def preload_exercises(query) do
    Swish.Repo.all from l in Swish.Lesson, join: e in assoc(l, :exercises), preload: [exercises: e]
  end

This is what i used to seed the data:

Repo.insert %Exercise{ nativeword: "hola", foreignword: "Hello", lesson_id: 3}
Repo.insert %Exercise{ nativeword: "adios", foreignword: "goodbye", lesson_id: 3}

Repo.insert %Exercise{ nativeword: "uno", foreignword: "one", lesson_id: 4}
Repo.insert %Exercise{ nativeword: "dos", foreignword: "two", lesson_id: 4}

Is there any way to remedy this? Thanks!

The error message is self descripting - list of Exercises is not queryable - you have to preload each Exercise within the list.

2 Likes

Your preload_exercises function does not return a query, but a list of already loaded exercises. You assume it returns a query and pass it to Repo.all once again and then it blows up.

3 Likes

Hello all,
I tried to follow Writing Web-facing tutorial but I got this:
protocol Ecto.Queryable not implemented for Staff, the given module does not exist

here is what student_controller looks like :

 defmodule InsWeb.ENR.StudentController do
  use InsWeb, :controller

  alias Ins.ENR
  alias Ins.ENR.Student

  plug :require_existing_staff
  plug :authorize_student when action in [:edit, :update, :delete]

  
  def index(conn, _params) do
    students = ENR.list_students()
    render(conn, "index.html", students: students)
  end

  def new(conn, _params) do
    changeset = ENR.change_student(%Student{})
    render(conn, "new.html", changeset: changeset)
  end

  def create(conn, %{"student" => student_params}) do
    case ENR.create_student(conn.assigns.current_staff, student_params) do
      {:ok, student} ->
        conn
        |> put_flash(:info, "Student created successfully.")
        |> redirect(to: enr_student_path(conn, :show, student))
      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end

  def show(conn, %{"id" => id}) do
    student = ENR.get_student!(id)
    render(conn, "show.html", student: student)
  end

  def edit(conn, %{"id" => id}) do
    student = ENR.get_student!(id)
    changeset = ENR.change_student(student)
    render(conn, "edit.html", student: student, changeset: changeset)
  end

  def update(conn, %{"id" => id, "student" => student_params}) do
    student = ENR.get_student!(id)

    case ENR.update_student(conn.assigns.student, student_params) do
      {:ok, student} ->
        conn
        |> put_flash(:info, "Student updated successfully.")
        |> redirect(to: enr_student_path(conn, :show, student))
      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "edit.html", student: student, changeset: changeset)
    end
  end

  def delete(conn, %{"id" => id}) do
    student = ENR.get_student!(id)
    {:ok, _student} = ENR.delete_student(conn.assigns.student)

    conn
    |> put_flash(:info, "Student deleted successfully.")
    |> redirect(to: enr_student_path(conn, :index))
  end

  ###############################################################################

  defp require_existing_staff(conn, _) do
    staff = ENR.ensure_staff_exists(conn.assigns.current_user)
    assign(conn, :current_staff, staff)
  end

  defp authorize_student(conn, _) do
    student = ENR.get_student!(conn.params["id"])

    if conn.assigns.current_staff.id == student.staff_id do
      assign(conn, :student, student)
    else
      conn
      |> put_flash(:error, "You can't modify that student")
      |> redirect(to: enr_student_path(conn, :index))
      |> halt()
    end
  end

end

student.ex looks like that:

defmodule Ins.ENR.Student do
  use Ecto.Schema
  import Ecto.Changeset
  alias Ins.ENR.{Student, Satff}


  schema "students" do
    field :birthdate, :date
    field :email, :string
    field :firstname, :string
    field :lastname, :string
    field :views, :integer
    belongs_to :staff, Staff

    timestamps()
  end

  @doc false
  def changeset(%Student{} = student, attrs) do
    student
    |> cast(attrs, [:firstname, :lastname, :birthdate, :email, :views])
    |> validate_required([:firstname, :lastname, :birthdate, :email, :views])
    |> unique_constraint(:email)
  end
end

You have a typo in student.ex:

alias Ins.ENR.{Student, Satff}
                         ^^

This is probably meant to say:

alias Ins.ENR.{Student, Staff}
3 Likes

Opps…thank you! @NobbZ