Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Erlang Dynamic Record Editing

I'm storing some data in mnesia, and I'd like to be able to change most of the values involved.

The naive

change(RecordId, Slot, NewValue) ->
    [Rec] = do(qlc:q([X || X <- mnesia:table(rec), X#rec.id =:= RecordId])),
    NewRec = Rec#rec{Slot=NewValue},
    F = fun() -> mnesia:write(NewRec) end,
    {atomic, Val} = mnesia:transaction(F),
    Val.

doesn't do it; the compiler complains that Slot is not an atom or _. Is there a way to express a general slot editing function as above, or am I going to be stuck defining a whole bunch of change_slots?

A marginally better approach is to pull out the insert and find pieces

atomic_insert(Rec) ->
    F = fun() -> mnesia:write(Rec) end,
    {atomic, Val} = mnesia:transaction(F),
    Val.

find(RecordId) -> 
    [Rec] = do(qlc:q([X || X <- mnesia:table(rec), X#rec.id =:= RecordId])),
    Rec.

change(RecordId, name, NewValue) ->
    Rec = find(RecordId),
    NewRec = Rec#rec{name=NewValue},
    atomic_insert(NewRec);
change(RecordId, some_other_property, NewValue) ->
    Rec = find(RecordId),
    NewRec = Rec#rec{some_other_property=NewValue},
    ...

but there's still a bit of code duplication there. Is there any way to abstract that pattern out? Is there an established technique to allow records to be edited? Any ideas in general?

like image 894
Inaimathi Avatar asked Jan 17 '23 13:01

Inaimathi


2 Answers

Since records are represented by tuples, you could try using tuple operations to set individual values.

-module(rec).
-export([field_num/1, make_rec/0, set_field/3]).
-record(rec, {slot1, slot2, slot3}).

make_rec() ->
  #rec{slot1=1, slot2=2, slot3=3}.

field_num(Field) ->
  Fields = record_info(fields, rec),
  DifField = fun (FieldName) -> Field /= FieldName end,
  case length(lists:takewhile(DifField, Fields)) of
    Length when Length =:= length(Fields) ->
      {error, not_found};
    Length ->
      Length + 2
  end.

set_field(Field, Value, Record) ->
  setelement(field_num(Field), Record, Value).

set_field will return an updated record:

Eshell V5.9.1  (abort with ^G)
1> c(rec).
{ok,rec}
2> A = rec:make_rec().
{rec,1,2,3}
3> B = rec:set_field(slot3, other_value, A).
{rec,1,2,other_value}
like image 93
kjw0188 Avatar answered Jan 19 '23 04:01

kjw0188


You can also define change as a macro (especially if it used only inside the module):

-define(change(RecordId, Slot, NewValue),
        begin
            [Rec] = do(qlc:q([X || X <- mnesia:table(rec), X#rec.id =:= RecordId])),
            NewRec = Rec#rec{Slot=NewValue},
            F = fun() -> mnesia:write(NewRec) end,
            {atomic, Val} = mnesia:transaction(F),
            Val
        end).

Usage:

test(R, Id) ->
    ?change(Id, name, 5).

With macro you can also pass _ as a field (good for pattern matching).

like image 34
Ed'ka Avatar answered Jan 19 '23 03:01

Ed'ka