Call after_save function before Repo.insert

Hey there,
Hope you’ll are doing great.
Actually in my code I want to run after_save before Repo.insert.
When I call it before Repo.insert it does not work and when I call it after Repo.insert then it does. Seeking help so hardly :slightly_smiling_face:
Here’s my 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

First of all, format your code, please.

Second, the changeset function doesn’t usually return an :ok tuple. Look in the what’s the return value’s type.

Third, please never ever insert records in a database iteratively before making sure this is the only way possible.

Hey @krasenyp,
Thanks for your response.
You’re right but when i did after_save after insert it does work but it does not return {:ok, files} for multiple files insertion.
Here’s the code in which after_save does work for only one file upload and return {:ok, files}. And when I upload more than one file insertion happends, after_save also but do not recieve any message of {:ok, files}

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

    IO.inspect(allFiles)
    {:ok, allFiles}
  end

What output do you get?

In this code. I’m not an error the files are easily inserting.
But my after_save function cannot get the value in the shape of map from changeset.

However on the same spot when I call my after_Save function after Repo.insert. It does work because getting map from Repo.insert.

Here’s my output:

INSERT INTO `files` (`name`,`size`,`type`,`url`,`user_id`,`inserted_at`,`updated_at`) VALUES (?,?,?,?,?,?,?) ["tdyhf.jpg", 58118, "image/jpeg", "/uploads/b7a23917-c5de-431e-8574-dcff00e3f325.jpg", 1, ~N[2021-10-15 15:02:02], ~N[2021-10-15 15:02:02]]
[debug] QUERY OK db=0.0ms idle=47.0ms
INSERT INTO `files` (`name`,`size`,`type`,`url`,`user_id`,`inserted_at`,`updated_at`) VALUES (?,?,?,?,?,?,?) ["pd36-2-pia03654.jpg", 233077, 
"image/jpeg", "/uploads/c2020c7b-77a4-47a8-9c00-07dc9d7445f2.jpg", 1, ~N[2021-10-15 15:02:02], ~N[2021-10-15 15:02:02]]
[
  ok: %Initium.Files.File{
    __meta__: #Ecto.Schema.Metadata<:loaded, "files">,
    alt_text: nil,
    caption: nil,
    description: nil,
    dimensions: nil,
    id: 86,
    inserted_at: ~N[2021-10-15 15:02:02],
    name: "tdyhf.jpg",
    size: 58118,
    title: nil,
    type: "image/jpeg",
    updated_at: ~N[2021-10-15 15:02:02],
    url: "/uploads/b7a23917-c5de-431e-8574-dcff00e3f325.jpg",
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 1
  },
  ok: %Initium.Files.File{
    __meta__: #Ecto.Schema.Metadata<:loaded, "files">,
    alt_text: nil,
    caption: nil,
    description: nil,
    dimensions: nil,
    id: 87,
    inserted_at: ~N[2021-10-15 15:02:02],
    name: "pd36-2-pia03654.jpg",
    size: 233077,
    title: nil,
    type: "image/jpeg",
    updated_at: ~N[2021-10-15 15:02:02],
    url: "/uploads/c2020c7b-77a4-47a8-9c00-07dc9d7445f2.jpg",
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 1
  }
]

Have you tried putting IO.inspect calls inside after_save and checking if it gets called and whether the data before/after func.(file) matches what you expect?

I’d also suggest making your function more strict:

after_save(error, func)after_save({:error, _} = error, func)

This way you’ll get a FunctionClauseError if changeset returns something else than an ok/error tuple, rather than silently ignoring it.

Actually I knew it because the problem is that changeset is not returning the values as map. That’s why they are not going to after_save

[error] GenServer #PID<0.6025.0> terminating
** (FunctionClauseError) no function clause matching in Initium.Files.after_save/2
    (initium 0.1.0) lib/initium/files.ex:119: Initium.Files.after_save(#Ecto.Changeset<action: nil, changes: %{name: "oyuiytuejkjhmfh.jpg", size: 262500, type: "image/jpeg", url: "/uploads/0004de0a-b6a8-4ace-bc5a-96f4394dd1d4.jpg", user_id: 1}, errors: [], data: #Initium.Files.File<>, valid?: true>, #Function<3.109481260/1 in InitiumWeb.FileLive.FormComponent.save_file/3>)

It sounds like you should modify your function to work with a changeset. It’s possible to convert a changeset to a struct using apply_changes, but if you do that, you can’t pass it to Repo.insert anymore. The Ecto doc has more details on how to work with changesets: get_field, put_change, etc.

Thanks for your kind time, It really means a lot.
get_field & put_change works on the output provided by the changeset. Like in my case I want my changeset to return the value as map. Or after returning convert the values of changeset to map and pass them to after_save function.
Here’s the output I’m getting from my changeset:

[
  #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
  >
]

My after_save function will operate when It’ll receive the value in the form of map. That’s why Its ignoring the values provided by changeset.
Here’s the 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

Here we can in the pipe the value generated by changeset is going to after_save, But its ignoring because its not a tuple or map :slightly_frowning_face:

Thanks Again

My after_save function will operate when It’ll receive the value in the form of map. That’s why Its ignoring the values provided by changeset.

I understand, but why don’t you change your after_save function?

Hi @dom
Sorry I was out.
There’s no bug in after_save function. Its working absolutely fine. The problem I’m facing from a long while is that when I call after_save function after Repo.insert It does work and store every image thumbnail but do not return {:ok, files} or message to the index.

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

And on the same spot when i call after_save before Repo.insert then I receive the message of saving but after_save stops working.

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