Protocol Enumerable not implemented for ProjectName.Files.File of type Atom

Greeting!
I’m receiving the error that protocol enumerable not implements on type atom. In the schema file every field is inserting as an atom. And I want to convert my values getting from changeset into map. But receiving this error.

def create_files(files \\ [], after_save \\ [&{:ok, &1}]) do
    for file <- files do
      changeset =
        File.changeset(
          %File{},
          file
        )

      myfiles =
        Enum.map(File, & &1[changeset])
        |> after_save(after_save)
        |> Repo.insert()

      IO.inspect(myfiles)
    end

Kind Regards

This error means you’re trying to iterate over something that isn’t a list or a list-like structure. For instance:

​Protocol Enumerable not implemented for ProjectName.Files.File of type Atom

is coming from:

Enum.map(File, & &1[changeset])

The first argument of Enum.map should be a list or list-like structure (Map, etc).

1 Like

Got it,
Actually I’m doing this to convert my changeset value into map and send it to after_save function.
As it is not returning the map that’s why i used “Enum.map(File, & &1[changeset])”
And when I remove this File inside the Enum.map It says “calling to undefined or private function”

You should use other tools…

Like prepare_changes

https://hexdocs.pm/ecto/Ecto.Changeset.html#prepare_changes/2

or Ecto.Multi and Repo.transaction.

I recommend breaking apart this code into more understandable pieces, at least until you get it working. Pipes (|>) are powerful, but they can also obscure data flow a little. Instead of a series of pipes, try a series of individual statements with temporary variables. For each of those statements, you should understand:

  • what shapes of data it expects as input
  • what shapes of data it produces as output

Let’s walk through the code:

def create_files(files \\ [], after_save \\ [&{:ok, &1}]) do

The default value for files is odd (is anyone going to call create_files with no arguments?), but not a problem.

The default value for after_save is more confusing - as written, it is:

  • a list
  • with one element
  • that is an anonymous function that wraps its input in an {:ok, _} tuple

It’s hard to say more without seeing the code of the after_save function. Is the intent to pass a list of callback functions to create_files?

      changeset =
        File.changeset(
          %File{},
          file
        )

Nothing unusual about this. changeset is bound to a %Ecto.Changeset{} struct.

      myfiles =
        Enum.map(File, & &1[changeset])
        |> after_save(after_save)
        |> Repo.insert()

First comment: this code is inside a loop over files. The plural name may indicate that this code should go outside the loop.

Second comment: the purpose of that Enum.map is unclear. What does the after_save function expect as its first argument?

Third comment: the intent of the anonymous function passed to Enum.map also unclear. & &1[changeset] tries to use the [] operator on the given element with changeset as the key. While this is allowed it is likely not what you intended, as I’m unclear where such a list-of-maps-keyed-with-Ecto.Changesets would come from.

1 Like

Hey, Thank you soo much for your time to guide me regarding this. It really means a lot.

Here’s the new changes as you mentioned that I must remove the extra or wasted code. It works, but still not getting the values in the map from File.changeset(file), that’s why my after_save isn’t working on the values provided by the changeset.

Here’s the new code:

  def create_files(files \\ [], after_save \\ &{:ok, &1}) do
    allFiles =
      for file <- files do
        %File{}
        |> File.changeset(file)
        |> after_save(after_save)
        |> Repo.insert()
      end

    IO.inspect(allFiles)

    # my_files = {allFiles, []}
    {:ok, files}
  end

  defp after_save({:ok, file}, func) do
    {:ok, _file} = func.(file)
  end

  defp after_save(error, _func) do
    error
  end

The shape of values given by changeset in the output is:

[
  #Ecto.Changeset<
    action: nil,
    changes: %{
      name: "calm.jpg",
      size: 49472,
      type: "image/jpeg",
      url: "/uploads/6066ee3d-815f-4e93-812d-3b1292b72959.jpg",
      user_id: 1
    },
    errors: [],
    data: #Initium.Files.File<>,
    valid?: true
  >,
  #Ecto.Changeset<
    action: nil,
    changes: %{
      name: "bgrdg.jpg",
      size: 42531,
      type: "image/jpeg",
      url: "/uploads/4752565d-d0ea-456c-b583-88e9d5adcf65.jpg",
      user_id: 1
    },
    errors: [],
    data: #Initium.Files.File<>,
    valid?: true
  >
]

Here in the code we can see that the returning values aren’t in a shape of a map. The changeset is not returning the map.

Thanks

1 Like

The after_save function isn’t getting called because its arguments don’t match. Let’s walk through how that happens.

We’ll start by regrouping a little; a common idiom in Elixir is to make a function that does something to every element of a list by looping with a function that does something to one element:

  def create_files(files \\ [], after_save \\ &{:ok, &1}) do
    allFiles =
      for file <- files do
        create_file(file, after_save)
      end

    IO.inspect(allFiles)

    {:ok, files}
  end

  defp create_file(file, after_save) do
    %File{}
    |> File.changeset(file)
    |> after_save(after_save)
    |> Repo.insert()
  end

Now split apart the pipe in create_file:

defp create_file(file, after_save) do
  changeset = File.changeset(%File{}, file)

  changeset_after_callback = after_save(changeset, after_save)

  Repo.insert(changeset_after_callback)
end

Shapes:

  • changeset is an Ecto.Changeset struct

  • the first head of after_save does not match, so the second runs - thus changeset_after_callback is the same Ecto.Changeset struct as changeset.

  • Repo.insert can return either {:ok, %File{}} or {:error, %Ecto.Changeset{}}


Based on the shape that after_save expects in that first head, I suspect you want to move the after_save call to… after the save, done by Repo.insert:

defp create_file(file, after_save) do
  changeset = File.changeset(%File{}, file)

  insert_result = Repo.insert(changeset)

  after_save(insert_result, after_save)
end

Shapes in this version:

  • changeset is an Ecto.Changeset struct

  • Repo.insert can return either {:ok, %File{}} or {:error, %Ecto.Changeset{}} for insert_result

  • if the insert succeeded, the first head of after_save will match and call the after_save callback. Otherwise, the error will be returned as-is via the second head of after_save.

And then the code can be smooshed back together into a pipe:

defp create_file(file, after_save) do
  %File{}
  |> File.changeset(file)
  |> Repo.insert()
  |> after_save(after_save)
end

Edit: by the way, did you mean allFiles in the return value of create_files?

    {:ok, files}
  end
2 Likes

Hey @al2o3cr
Sorry, I was out.
Your guidance is really outstanding & helping a lot.
Exactly you’re absolutely right that when we call the after_save function after Repo.insert It does work, no doubt.
But actually the problem I’m facing from a long while is that when I call after_save after Repo.insert it does not return the {:ok, files}.
Like after insertion and after_save I’m not receiving any message. But on the same case when i swpa them and call after_save first and Repo.insert later then I recieve the message but after_save stops working.

I hope you understand my problem now, looking forward for your further help :slight_smile:

Kind Regards