How to update a key's value with Mnesia and Memento without overwriting the other values?

I’ve created a module of CRUD operations, mostly one-liners, but I’m stuck at creating a general purpose update function.

Goals:

  1. Update the value of any key or keys in the row, independently of each other.
  2. Insert and or update the :updated_at key.
  3. Only update the keys specified by the update functions arguments

My approach so far has been something along these lines (pseudo code):

......
defmodule Todo do
  use Memento.Table,
    attributes: [:id, :name, :priority, :updated_at],
    type: :ordered_set,
    autoincrement: true
.....
  def update(id, changes = %{}) do
    Memento.transaction do
      case get_by_id(id) do
        %Todo{} = todo ->
          todo
          |> adjust( changes)
          |> Map.put!(:updated_at, NaiveDateTime.utc_now()
 
        _ ->
          {:error, :not_found}
      end
    end
    
    defp adjust(%Todo{} = todo, changes) do
       todo
         |> Map.update!(changes))
         |> Todo.write()
    end

    def get_by_id(id) do
          Memento.transaction(fn -> Memento.Query.read(Todo, id) end)
    end

Elixir school demonstrates updates with Mnesia.write/1 but this overwrites the whole row.

The other solutions I found are either over my head or in Erlang:
https://stackoverflow.com/questions/63048415/elixir-mnesia-what-is-the-most-concise-way-to-update-a-set-of-value-in-an-el

https://stackoverflow.com/questions/1820996/erlang-mnesia-updating-a-single-field-value-in-a-row

Thanks to @sheharyarn for providing the solution over at github:
https://github.com/sheharyarn/memento/issues/30

def update(id, %{} = changes) do
  Memento.transaction(fn ->
    case Memento.Query.read(Todo, id, lock: :write) do
      %Todo{} = todo ->
        todo
        |> struct(changes)
        |> Map.put(:updated_at, NaiveDateTime.utc_now())
        |> Memento.Query.write()
        |> then(&{:ok, &1})

      nil ->
        {:error, :not_found}
    end
  end)
end

1 Like