Skip to content

Commit

Permalink
Refactor to single parameterized datastore module
Browse files Browse the repository at this point in the history
 - Work around runtime limitations of records with
helper functions for fields, queries and prestore

 - Retarget client code at single module

 - Remove separate data store modules

 - Remove use of functions in includes

 - Thank you Zed and Adam for StackOverflow help:
http://stackoverflow.com/questions/1550850/how-do-you-debug-functions-from-includes-in-erlang
  • Loading branch information
AlainODea committed Oct 11, 2009
1 parent 1e4e0d9 commit beb68b6
Show file tree
Hide file tree
Showing 12 changed files with 103 additions and 78 deletions.
45 changes: 0 additions & 45 deletions include/scrumjet_datastore.hrl

This file was deleted.

4 changes: 1 addition & 3 deletions src/scrumjet.app
Expand Up @@ -6,10 +6,8 @@
scrumjet_app,
scrumjet_sup,
scrumjet_deps,
scrumjet_task,
scrumjet_category,
scrumjet_datastore,
scrumjet_category_task,
scrumjet_board,
scrumjet_board_category,
scrumjet_resource,
scrumjet_tasks_resource,
Expand Down
4 changes: 1 addition & 3 deletions src/scrumjet.erl
Expand Up @@ -23,9 +23,7 @@ start() ->
ensure_started(webmachine),
mnesia:create_schema([node()]),
ensure_started(mnesia),
scrumjet_task:start(),
scrumjet_category:start(),
scrumjet_board:start(),
scrumjet_datastore:start(),
scrumjet_category_task:start(),
scrumjet_board_category:start(),
application:start(scrumjet).
Expand Down
2 changes: 0 additions & 2 deletions src/scrumjet_board.erl

This file was deleted.

4 changes: 2 additions & 2 deletions src/scrumjet_board_resource.erl
Expand Up @@ -23,7 +23,7 @@ init([]) -> {ok, #context{}}.

resource_exists(ReqData, Context) ->
ID = wrq:disp_path(ReqData),
case scrumjet_board:find({id, ID}) of
case scrumjet_datastore:find(scrumjet_board, {id, ID}) of
[] -> {false, ReqData, Context#context{board=#scrumjet_board{id=ID}}};
[Board] -> {true, ReqData, Context#context{board=Board}}
end.
Expand Down Expand Up @@ -53,7 +53,7 @@ content_types_accepted(ReqData, Context) ->
from_webform(ReqData, Context=#context{board=Board}) ->
Params = mochiweb_util:parse_qs(wrq:req_body(ReqData)),
Title = proplists:get_value("title", Params, ""),
scrumjet_board:store(Board#scrumjet_board{title=Title}),
scrumjet_datastore:store(Board#scrumjet_board{title=Title}),
{true, ReqData, Context}.

-spec categories(string()) -> iolist().
Expand Down
2 changes: 0 additions & 2 deletions src/scrumjet_category.erl

This file was deleted.

4 changes: 2 additions & 2 deletions src/scrumjet_category_resource.erl
Expand Up @@ -24,7 +24,7 @@ init([]) -> {ok, #context{}}.

resource_exists(ReqData, Context) ->
ID = wrq:disp_path(ReqData),
case scrumjet_category:find({id, ID}) of
case scrumjet_datastore:find(scrumjet_category, {id, ID}) of
[] -> {false, ReqData, Context#context{category=
#scrumjet_category{id=ID}}};
[Category] -> {true, ReqData, Context#context{category=Category}}
Expand Down Expand Up @@ -62,7 +62,7 @@ content_types_accepted(ReqData, Context) ->
from_webform(ReqData, Context=#context{category=Category}) ->
Params = mochiweb_util:parse_qs(wrq:req_body(ReqData)),
Name = proplists:get_value("name", Params, ""),
scrumjet_category:store(Category#scrumjet_category{name=Name}),
scrumjet_datastore:store(Category#scrumjet_category{name=Name}),
{true, ReqData, Context}.

-spec tasks(string()) -> iolist().
Expand Down
79 changes: 79 additions & 0 deletions src/scrumjet_datastore.erl
@@ -0,0 +1,79 @@
-module (scrumjet_datastore).

-include("scrumjet.hrl").
-include_lib("stdlib/include/qlc.hrl").

-export([start/0, store/1, find/2, range/3]).

-define (TYPES, [scrumjet_task, scrumjet_category, scrumjet_board]).

%% Internal Helper Functions

fields(scrumjet_task) -> record_info(fields, scrumjet_task);
fields(scrumjet_category) -> record_info(fields, scrumjet_category);
fields(scrumjet_board) -> record_info(fields, scrumjet_board).

prestore(Record=#scrumjet_task{ctime=undefined}) ->
Now = erlang:now(),
Record#scrumjet_task{ctime=Now, mtime=Now};
prestore(Record=#scrumjet_category{ctime=undefined}) ->
Now = erlang:now(),
Record#scrumjet_category{ctime=Now, mtime=Now};
prestore(Record=#scrumjet_board{ctime=undefined}) ->
Now = erlang:now(),
Record#scrumjet_board{ctime=Now, mtime=Now};
prestore(Record=#scrumjet_task{}) ->
Now = erlang:now(),
Record#scrumjet_task{mtime=Now};
prestore(Record=#scrumjet_category{}) ->
Now = erlang:now(),
Record#scrumjet_category{mtime=Now};
prestore(Record=#scrumjet_board{}) ->
Now = erlang:now(),
Record#scrumjet_board{mtime=Now}.

select(scrumjet_task, {id, Id}) ->
qlc:q([M || M <- mnesia:table(scrumjet_task),
M#scrumjet_task.id =:= Id]);
select(scrumjet_category, {id, Id}) ->
qlc:q([M || M <- mnesia:table(scrumjet_category),
M#scrumjet_category.id =:= Id]);
select(scrumjet_board, {id, Id}) ->
qlc:q([M || M <- mnesia:table(scrumjet_board),
M#scrumjet_board.id =:= Id]).

create_table(Type) ->
try
set = mnesia:table_info(Type, type)
catch
exit:{aborted, {no_exists, Type, type}} ->
{atomic, ok} = mnesia:create_table(Type,
[{attributes, fields(Type)},
{type, set},
{disc_copies, [node()]}])
end.

%% Public API Functions

start() -> [create_table(Type) || Type <- ?TYPES].

store(Record) ->
F = fun() ->
mnesia:write(prestore(Record))
end,
mnesia:transaction(F).

find(Type, Params) ->
F = fun() ->
Query = select(Type, Params),
qlc:eval(Query)
end,
{atomic, Records} = mnesia:transaction(F),
Records.

range(Type, Start, End) ->
F = fun() ->
mnesia_ext:limit(Type, Start, End - Start)
end,
{atomic, Records} = mnesia:transaction(F),
Records.
2 changes: 0 additions & 2 deletions src/scrumjet_task.erl

This file was deleted.

7 changes: 4 additions & 3 deletions src/scrumjet_task_resource.erl
Expand Up @@ -34,7 +34,7 @@ content_types_provided(ReqData, Context) ->

resource_exists(ReqData, Context) ->
ID = wrq:disp_path(ReqData),
case scrumjet_task:find({id, ID}) of
case scrumjet_datastore:find(scrumjet_task, {id, ID}) of
[] -> {false, ReqData, Context#context{task=#scrumjet_task{id=ID}}};
[Task] -> {true, ReqData, Context#context{task=Task}}
end.
Expand Down Expand Up @@ -73,8 +73,9 @@ from_webform(ReqData, Context=#context{task=Task}) ->
Params = mochiweb_util:parse_qs(wrq:req_body(ReqData)),
Headline = proplists:get_value("headline", Params, ""),
Description = proplists:get_value("description", Params, ""),
scrumjet_task:store(Task#scrumjet_task{description=Description,
headline=Headline}),
scrumjet_datastore:store(
Task#scrumjet_task{description=Description,
headline=Headline}),
{true, ReqData, Context}.

-spec categories(string()) -> iolist().
Expand Down
2 changes: 1 addition & 1 deletion src/scrumjet_tasks_resource.erl
Expand Up @@ -101,7 +101,7 @@ range(undefined) -> undefined.

-spec tasks(integer(), integer) -> [sj_record()].
tasks(Start, End) ->
scrumjet_task:range(Start, End).
scrumjet_datastore:range(scrumjet_task, Start, End).

-spec query_range(string(), string()) -> {integer(), integer()}.
query_range(undefined, _) -> undefined;
Expand Down
26 changes: 13 additions & 13 deletions src/test_scrumjet_category.erl → src/test_scrumjet_datastore.erl
@@ -1,4 +1,4 @@
-module(test_scrumjet_category).
-module(test_scrumjet_datastore).

-include_lib("eunit/include/eunit.hrl").
-include("scrumjet.hrl").
Expand All @@ -10,9 +10,9 @@ setup() ->
mnesia:stop(),
mnesia:delete_schema([node()]),
mnesia:create_schema([node()]),
scrumjet_category:start_link().
scrumjet_datastore:start().

cleanup(_Pid) -> catch scrumjet_category:shutdown().
cleanup(_Pid) -> ok.

happy_path_test_() ->
{setup,
Expand All @@ -23,9 +23,9 @@ happy_path_test_() ->

happy_path_generator(_Pid) ->
{timeout, 1, [
fun() -> scrumjet_category:store(
fun() -> scrumjet_datastore:store(
#scrumjet_category{id="family", name="Family Stuff"}) end,
fun() -> scrumjet_category:find({id,"family"}) end
fun() -> scrumjet_datastore:find(scrumjet_category, {id,"family"}) end
]}.

% 2. Verify direct outputs of happy path
Expand All @@ -40,10 +40,10 @@ verify_happy_path_test_() ->

verify_happy_path_generator(_Pid) ->
{timeout, 1, [
?_assertEqual(ok, scrumjet_category:store(
?_assertEqual(ok, scrumjet_datastore:store(
#scrumjet_category{id="family", name="Family Stuff"})),
?_assertMatch([#scrumjet_category{id="family", name="Family Stuff"}],
scrumjet_category:find({id,"family"}))
scrumjet_datastore:find(scrumjet_category, {id,"family"}))
]}.

% 3. Verify Alternate Paths
Expand All @@ -58,16 +58,16 @@ verify_alternate_test_() ->

verify_alternate_generator(_Pid) ->
{timeout, 1, [
?_assertMatch([], scrumjet_category:find({id,"family"})),
?_assertMatch([], scrumjet_category:find({id,"work"})),
?_assertEqual(ok, scrumjet_category:store(
?_assertMatch([], scrumjet_datastore:find(scrumjet_category, {id,"family"})),
?_assertMatch([], scrumjet_datastore:find(scrumjet_category, {id,"work"})),
?_assertEqual(ok, scrumjet_datastore:store(
#scrumjet_category{id="family", name="Family Stuff"})),
?_assertEqual(ok, scrumjet_category:store(
?_assertEqual(ok, scrumjet_datastore:store(
#scrumjet_category{id="work", name="Work"})),
?_assertMatch([#scrumjet_category{id="family", name="Family Stuff"}],
scrumjet_category:find({id,"family"})),
scrumjet_datastore:find(scrumjet_category, {id,"family"})),
?_assertMatch([#scrumjet_category{id="work", name="Work"}],
scrumjet_category:find({id,"work"}))
scrumjet_datastore:find(scrumjet_category, {id,"work"}))
]}.

% 4. Control Indirect Inputs of SUT via Test Stub
Expand Down

0 comments on commit beb68b6

Please sign in to comment.