Protocol Ecto.Queryable not implemented for MyApp.MyContext.MySchema{__meta__: #Ecto.Schema.Metadata

Hello,

I have the following error when I try to execute change_question:

protocol Ecto.Queryable not implemented for %MyApp.Quiz.Question{meta: #Ecto.Schema.Metadata…

def change_question(%Question{} = question) do
  question
  |> with_collection_assoc()
  |> Question.changeset(%{})
end

def with_collection_assoc(query) do
  from q in query, preload: [:collection] # <- fails here
end

defmodule MyApp.Quiz.Question do
  use MyApp.Schema
  import Ecto.Changeset

  schema "questions" do
    field :title, :map
    field :type, :string
    field :text, :map
    belongs_to :collection, MyApp.Quiz.Collection
  end

  def changeset(question, attrs) do
    question
    |> cast(attrs, [:title, :type, :text])
    |> cast_assoc(:collection)
    |> validate_required([:collection, :title, :type, :text])
  end

Anyone can explain what the error is about?
Isn’t a Schema struct Queryable?

Hi @thojanssens1

The query expects an :atom or :module_name, but you’re passing a Struct (map) which is why it fails and also you need to pass a complete struct for your changeset, but now you’re just passing the query.

To verify:

iex(1)> i module_name
iex(2)> i %module_name{}

Try this in your shell and that shows the clear difference between both. Replace module_name above with your relevant module name.

Thanks.

1 Like

Thank you for the help:)
I replaced |> from q in query, preload: [:collection] by
|> Repo.preload(:collection) and that seems to work I think.

But I have an “is invalid” error when submitting the form. The user can choose a “collection” for the question (as you can see in the schema posted above, a question belongs to a collection), and the value of the selected option is the collection id.

<%= label f, :collection %>
<%= select f, :collection, collection_select_options(@collections),
  prompt: "Choose a collection" %>
<%= error_tag f, :collection %>

HTML generated:

<select id="question_collection" name="question[collection]">
  <option value="">Choose a collection</option>
  <option value="7c488360-971f-45a3-895f-eab8bd37be44">Collection 1</option>
  <option value="c15ea963-dc9b-476d-a30b-dd4f754c295b">Collection 2</option>
</select>

So the following will not work?

  def changeset(question, attrs) do
    question
    |> cast(attrs, [:title, :type, :text])
    |> cast_assoc(:collection) # <- is this generating the "is invalid" error?
    |> validate_required([:collection, :title, :type, :text])
  end

I’m very confused on how to work with forms and associations. I think I have an error because I have an id for the collection field and not a whole collection struct, but not sure.

My controller action on submit:

def create(conn, %{"question" => question_params}) do
  case Quiz.create_question(question_params) do
     # code

And the create_question function in the context:

def create_question(attrs \\ %{}) do
  %Question{}
  |> Repo.preload(:collection) # <- is this even necessary?
  |> Question.changeset(attrs)
  |> Repo.insert()
end

Hi @thojanssens1,

If you have already referenced the table in your migration, it is not necessary. A preload with id should work. But, since you want check with association constraint it’s needed to pass it into cast_assoc.

References in your migration gives you the constraint at DB level.

I also found some helpful articles for working with forms:

https://hackernoon.com/introduction-fe138ac6079d

Thanks.

2 Likes

Thank you. I gave up on cast_assoc and instead, I cast and validate_required the foreign key collection_id.

This works as expected. I’m just confused that another developer willing to use this changeset in some other part of the application will have to know that he has to pass the foreign key and not the associated whole struct.

In other words, it would have been nice to have a changeset that works in both cases where you pass the foreign key or when you pass the associated struct.

1 Like