Skip to content

Instantly share code, notes, and snippets.

@stolen
Created September 27, 2012 09:33
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save stolen/3793122 to your computer and use it in GitHub Desktop.
Save stolen/3793122 to your computer and use it in GitHub Desktop.
gen_server with self-upgrading state prototype
-module(gs_up).
-export([init/1, handle_call/3, terminate/2]).
-record(state, {
own_fields,
field1,
field2,
field3
}).
init([F1, F3]) ->
{ok, #state{own_fields = record_info(fields, state),
field1 = F1, field3 = F3}}.
terminate(_,_) -> ok.
handle_call(Call, From, State) ->
NewState = case element(2, State) == record_info(fields, state) of
true -> State;
false -> migrate(State)
end,
do_handle_call(Call, From, NewState).
do_handle_call(_, _, #state{} = State) ->
{reply, state_to_pl(State), State}.
state_to_pl(State) when is_tuple(State) ->
Fields = element(2, State),
tl(lists:zip(Fields, tl(tuple_to_list(State)))).
state_from_pl(PL) ->
ZeroState = #state{own_fields = record_info(fields, state)},
FieldsToInherit = state_to_pl(ZeroState),
InheritedValues = [proplists:get_value(Field, PL, Default) || {Field, Default} <- FieldsToInherit],
list_to_tuple([element(1, ZeroState), element(2, ZeroState) | InheritedValues]).
migrate(OldState) ->
state_from_pl(state_to_pl(OldState)).
@stolen
Copy link
Author

stolen commented Sep 27, 2012

37> gen_server:start_link({local, gs_up}, gs_up, [v1, v3], []).
{ok,<0.152.0>}
38> "Old code state", gen_server:call(gs_up, ok).
[{field1,v1},{field2,undefined},{field3,v3}]
39> "Changed field order", c(gs_up), l(gs_up).   
{module,gs_up}
40> "Voila, valid state!", gen_server:call(gs_up, ok).
[{field2,undefined},{field1,v1},{field3,v3}]
41> "Removed field2", c(gs_up), l(gs_up).             
{module,gs_up}
42> "Still valid", gen_server:call(gs_up, ok).        
[{field1,v1},{field3,v3}]
43> "Put field2 back", c(gs_up), l(gs_up).    
{module,gs_up}
44> "Valid again!", gen_server:call(gs_up, ok).
[{field1,v1},{field2,undefined},{field3,v3}]

@vlm
Copy link

vlm commented Sep 27, 2012

handle_call(Call, From, #state{} = State) -> do_usual_stuff();
handle_call(Call, From, State) -> handle_call(Call, From, autoupgrade(State)).

@stolen
Copy link
Author

stolen commented Sep 27, 2012

handle_call(Call, From, #state{} = State) -> do_usual_stuff();
will not work properly (actually, will f**k the things up) if record length did not change (e.g. changing field order or removing a field and adding new one)

@stolen
Copy link
Author

stolen commented Sep 27, 2012

Unfortunately, Erlang does not allow matching record_info(fields, state).
With this possibility modifications to usual code would be just first case:

handle_call(C, F, State) when element(2, State) /= record_info(fields, state) ->
  handle_call(C, F, migrate(State));

Maybe it can be done in parse_transform, I will see it later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment