Skip to content

Commit

Permalink
::uuid() is now a valid Id type
Browse files Browse the repository at this point in the history
  • Loading branch information
Kevin Montuori committed Sep 17, 2012
1 parent 22cbbd2 commit 093d9f0
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 44 deletions.
64 changes: 34 additions & 30 deletions src/boss_db.erl
Expand Up @@ -266,36 +266,40 @@ validate_record(Record) ->
validate_record_types(Record) ->
Errors = lists:foldl(fun
({Attr, Type}, Acc) ->
Data = Record:Attr(),
GreatSuccess = case {Data, Type} of
{undefined, _} ->
true;
{Data, string} when is_list(Data) ->
true;
{Data, binary} when is_binary(Data) ->
true;
{{{D1, D2, D3}, {T1, T2, T3}}, datetime} when is_integer(D1), is_integer(D2), is_integer(D3),
is_integer(T1), is_integer(T2), is_integer(T3) ->
true;
{Data, integer} when is_integer(Data) ->
true;
{Data, float} when is_float(Data) ->
true;
{Data, boolean} when is_boolean(Data) ->
true;
{{N1, N2, N3}, timestamp} when is_integer(N1), is_integer(N2), is_integer(N3) ->
true;
{Data, atom} when is_atom(Data) ->
true;
{_Data, Type} ->
false
end,
if
GreatSuccess ->
Acc;
true ->
[lists:concat(["Invalid data type for ", Attr])|Acc]
end
case Attr of
id -> Acc;
_ ->
Data = Record:Attr(),
GreatSuccess = case {Data, Type} of
{undefined, _} ->
true;
{Data, string} when is_list(Data) ->
true;
{Data, binary} when is_binary(Data) ->
true;
{{{D1, D2, D3}, {T1, T2, T3}}, datetime} when is_integer(D1), is_integer(D2), is_integer(D3),
is_integer(T1), is_integer(T2), is_integer(T3) ->
true;
{Data, integer} when is_integer(Data) ->
true;
{Data, float} when is_float(Data) ->
true;
{Data, boolean} when is_boolean(Data) ->
true;
{{N1, N2, N3}, timestamp} when is_integer(N1), is_integer(N2), is_integer(N3) ->
true;
{Data, atom} when is_atom(Data) ->
true;
{_Data, Type} ->
false
end,
if
GreatSuccess ->
Acc;
true ->
[lists:concat(["Invalid data type for ", Attr])|Acc]
end
end
end, [], Record:attribute_types()),
case Errors of
[] -> ok;
Expand Down
25 changes: 18 additions & 7 deletions src/boss_db_mock_controller.erl
Expand Up @@ -48,14 +48,21 @@ handle_call({save_record, Record}, _From, [{Dict, IdCounter}|OldState]) ->
Type = element(1, Record),
TypeString = atom_to_list(Type),
{Id, IdCounter1} = case Record:id() of
id -> {lists:concat([Type, "-", IdCounter]), IdCounter + 1};
id -> case keytype(Record) of
uuid -> {lists:concat([Type, "-", uuid:to_string(uuid:v4())]), IdCounter};
_ -> {lists:concat([Type, "-", IdCounter]), IdCounter + 1}
end;
ExistingId ->
[TypeString, IdNum] = string:tokens(ExistingId, "-"),
Max = case list_to_integer(IdNum) of
N when N > IdCounter -> N;
_ -> IdCounter
end,
{lists:concat([Type, "-", IdNum]), Max + 1}
case keytype(Record) of
uuid -> {ExistingId, IdCounter};
_ ->
[TypeString, IdNum] = string:tokens(ExistingId, "-"),
Max = case list_to_integer(IdNum) of
N when N > IdCounter -> N;
_ -> IdCounter
end,
{lists:concat([Type, "-", IdNum]), Max + 1}
end
end,
NewAttributes = lists:map(fun
({id, _}) ->
Expand Down Expand Up @@ -88,6 +95,10 @@ code_change(_OldVsn, State, _Extra) ->
handle_info(_Info, State) ->
{noreply, State}.


keytype(Record) ->
proplists:get_value(id, Record:attribute_types(), unspecified).

do_find(Dict, Type, Conditions, Max, Skip, SortBy, SortOrder) ->
Tail = lists:nthtail(Skip,
lists:sort(fun(RecordA, RecordB) ->
Expand Down
14 changes: 7 additions & 7 deletions src/boss_news_controller.erl
Expand Up @@ -54,7 +54,7 @@ handle_call({set_watch, WatchId, TopicString, CallBack, UserInfo, TTL}, From, St
(SingleTopic, {ok, StateAcc, WatchListAcc}) ->
case re:split(SingleTopic, "\\.", [{return, list}]) of
[Id, Attr] ->
[Module, IdNum] = re:split(Id, "-", [{return, list}]),
[Module, IdNum] = re:split(Id, "-", [{return, list}, {parts, 2}]),
{NewState1, WatchInfo} = case IdNum of
"*" ->
SetAttrWatchers = case dict:find(Module, StateAcc#state.set_attr_watchers) of
Expand All @@ -75,7 +75,7 @@ handle_call({set_watch, WatchId, TopicString, CallBack, UserInfo, TTL}, From, St
end,
{ok, NewState1, [WatchInfo|WatchListAcc]};
_ ->
case re:split(SingleTopic, "-", [{return, list}]) of
case re:split(SingleTopic, "-", [{return, list}, {parts, 2}]) of
[_Module, _IdNum] ->
IdWatchers = case dict:find(SingleTopic, State#state.id_watchers) of
{ok, Val} -> Val;
Expand All @@ -96,7 +96,7 @@ handle_call({set_watch, WatchId, TopicString, CallBack, UserInfo, TTL}, From, St
end;
(_, Error) ->
Error
end, {ok, State, []}, re:split(TopicString, ", +", [{return, list}])),
end, {ok, State, []}, re:split(TopicString, ", +", [{return, list}, {parts, 2}])),
case RetVal of
ok -> {reply, RetVal, NewState#state{
watch_dict = dict:store(WatchId,
Expand Down Expand Up @@ -133,7 +133,7 @@ handle_call({extend_watch, WatchId}, _From, State0) ->
{reply, RetVal, NewState};
handle_call({created, Id, Attrs}, _From, State0) ->
State = prune_expired_entries(State0),
[Module | _IdNum] = re:split(Id, "-", [{return, list}]),
[Module | _IdNum] = re:split(Id, "-", [{return, list}, {parts, 2}]),
PluralModel = inflector:pluralize(Module),
{RetVal, State1} = case dict:find(PluralModel, State#state.set_watchers) of
{ok, SetWatchers} ->
Expand All @@ -156,7 +156,7 @@ handle_call({created, Id, Attrs}, _From, State0) ->
{reply, RetVal, State1};
handle_call({deleted, Id, OldAttrs}, _From, State0) ->
State = prune_expired_entries(State0),
[Module | _IdNum] = re:split(Id, "-", [{return, list}]),
[Module | _IdNum] = re:split(Id, "-", [{return, list}, {parts, 2}]),
PluralModel = inflector:pluralize(Module),
{RetVal, State1} = case dict:find(PluralModel, State#state.set_watchers) of
{ok, SetWatchers} ->
Expand All @@ -182,7 +182,7 @@ handle_call({deleted, Id, OldAttrs}, _From, State0) ->
{reply, RetVal, State1};
handle_call({updated, Id, OldAttrs, NewAttrs}, _From, State0) ->
State = prune_expired_entries(State0),
[Module | _IdNum] = re:split(Id, "-", [{return, list}]),
[Module | _IdNum] = re:split(Id, "-", [{return, list}, {parts, 2}]),
IdWatchers = case dict:find(Id, State#state.id_attr_watchers) of
{ok, Val} -> Val;
_ -> []
Expand Down Expand Up @@ -242,7 +242,7 @@ future_time(TTL) ->
MegaSecs * 1000 * 1000 + Secs + TTL.

activate_record(Id, Attrs) ->
[Module | _IdNum] = re:split(Id, "-", [{return, list}]),
[Module | _IdNum] = re:split(Id, "-", [{return, list}, {parts, 2}]),
Type = list_to_atom(Module),
DummyRecord = boss_record_lib:dummy_record(Type),
apply(Type, new, lists:map(fun
Expand Down
3 changes: 3 additions & 0 deletions src/boss_record_compiler.erl
Expand Up @@ -36,6 +36,9 @@ process_tokens([{']',_},{')',_},{dot,_}|_]=Tokens, TokenAcc, Acc) ->
process_tokens([{'-',N}=T1,{atom,N,module}=T2,{'(',_}=T3,{atom,_,_ModuleName}=T4,{',',_}=T5,
{'[',_}=T6,{var,_,'Id'}=T7|Rest], TokenAcc, []) ->
process_tokens(Rest, lists:reverse([T1, T2, T3, T4, T5, T6, T7], TokenAcc), []);
process_tokens([{'-',_N}=T1,{atom,_,module}=T2,{'(',_}=T3,{atom,_,_ModuleName}=T4,{',',_}=T5,
{'[',_}=T6,{var,_,'Id'}=T7,{'::',_},{atom,_,VarType},{'(',_},{')',_}|Rest], TokenAcc, []) ->
process_tokens(Rest, lists:reverse([T1, T2, T3, T4, T5, T6, T7], TokenAcc), [{'Id', VarType}]);
process_tokens([{',',_}=T1,{var,_,VarName}=T2,{'::',_},{atom,_,VarType},{'(',_},{')',_}|Rest], TokenAcc, Acc) ->
process_tokens(Rest, lists:reverse([T1, T2], TokenAcc), [{VarName, VarType}|Acc]);
process_tokens([H|T], TokenAcc, Acc) ->
Expand Down
59 changes: 59 additions & 0 deletions src/lib/uuid.erl
@@ -0,0 +1,59 @@
% Copyright (c) 2008, Travis Vachon
% All rights reserved.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are
% met:
%
% * Redistributions of source code must retain the above copyright
% notice, this list of conditions and the following disclaimer.
%
% * Redistributions in binary form must reproduce the above copyright
% notice, this list of conditions and the following disclaimer in the
% documentation and/or other materials provided with the distribution.
%
% * Neither the name of the author nor the names of its contributors
% may be used to endorse or promote products derived from this
% software without specific prior written permission.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
% A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
% OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
% SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
% PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
% LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
% SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
%
-module(uuid).
-export([v4/0, to_string/1, get_parts/1, to_binary/1]).
-import(random).

% Generates a random binary UUID.
v4() ->
v4(random:uniform(round(math:pow(2, 48))) - 1, random:uniform(round(math:pow(2, 12))) - 1, random:uniform(round(math:pow(2, 32))) - 1, random:uniform(round(math:pow(2, 30))) - 1).
v4(R1, R2, R3, R4) ->
<<R1:48, 4:4, R2:12, 2:2, R3:32, R4: 30>>.

% Returns a string representation of a binary UUID.
to_string(U) ->
lists:flatten(io_lib:format("~8.16.0b-~4.16.0b-~4.16.0b-~2.16.0b~2.16.0b-~12.16.0b", get_parts(U))).

% Returns the 32, 16, 16, 8, 8, 48 parts of a binary UUID.
get_parts(<<TL:32, TM:16, THV:16, CSR:8, CSL:8, N:48>>) ->
[TL, TM, THV, CSR, CSL, N].

% Converts a UUID string in the format of 550e8400-e29b-41d4-a716-446655440000
% (with or without the dashes) to binary.
to_binary(U)->
convert(lists:filter(fun(Elem) -> Elem /= $- end, U), []).

% Converts a list of pairs of hex characters (00-ff) to bytes.
convert([], Acc)->
list_to_binary(lists:reverse(Acc));
convert([X, Y | Tail], Acc)->
{ok, [Byte], _} = io_lib:fread("~16u", [X, Y]),
convert(Tail, [Byte | Acc]).

0 comments on commit 093d9f0

Please sign in to comment.