Learning to use Mnesia with Elixir, I created a table with multiple function (such as read, write...). One of them is to update a set of field (size from 1 to count - 1) without changing the rest of the data, and limiting the logic to the minimum inside the mnesia transaction.
During my search, I happen to find this : Erlang : Mnesia : Updating a single field value in a row though (code below). It is the same question but for Erlang, not for Elixir.
From what I understand, it works in Erlang as read returns a tuple which is directly set inside a record, which allows us to save specific data in the write action as they are named.
update_a(Tab, Key, Value) ->
fun() ->
[P] = mnesia:wread({Tab, Key}),
mnesia:write(P#pixel{a=Value})
end.
Where, for Elixir, Even though Records exist, it will just be a tuple where you can only change data in the index, and return the full tuple to the write action.
Table: {table_name, id, data1, data2, data3, data4}
changes = [{2, new_val_for_data1}, {4, new_val_for_data3}]
def handle_call({:update_and_read, table, {id, changes}}, _, state) do
{:atomic, new_object} =
:mnesia.transaction(fn ->
object =
:mnesia.wread({table, id})
|> List.first()
ret =
Enum.reduce(changes, object, fn {index, value}, acc ->
acc |> Tuple.delete_at(index) |> Tuple.insert_at(index, value)
end)
:mnesia.write(object)
ret
end)
{:reply, {:ok, new_object}, state}
end
Is it possible to have shorter function in Elixir (ideally, 2 lines like the one in Erlang) ?
Well, you can use Record
with some preliminary steps (like defining the record inside your app.)
defmodule Pixel do
require Record
Record.defrecord(:table_name, id: nil, data1: nil, data2: nil)
end
and in the module where you want to update the table
import Pixel
def handle_call({:update_and_read, table, {id, changes}}, _, state) do
# changes = [data2: new_val_for_data1, ...]
{:atomic, result} =
:mnesia.transaction(fn ->
case :mnesia.wread({table, id}) do
[object] ->
:mnesia.write(pixel(object, changes))
{:ok, object}
[] -> :error
end
end)
{:reply, result, state}
end
Another possibility would be to go through
changes = %{2 => new_val_for_data1, 4 => new_val_for_data3}
object
|> Tuple.to_list()
|> Enum.with_index()
|> Enum.map(fn {value, idx} -> Map.get(changes, idx, value) end)
|> List.to_tuple()
Another possibility would be to declare a macro that accepts a tuple, representing a table row, and a list of {idx, new_value}
tuples, and changes the respective elements in-place.