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.Changeset`s 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",
user_id: 1
},
errors: [],
data: #Initium.Files.File<>,
valid?: true
>,
#Ecto.Changeset<
action: nil,
changes: %{
name: "bgrdg.jpg",
size: 42531,
type: "image/jpeg",
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

Kind Regards