aboutsummaryrefslogtreecommitdiff
path: root/learn-you-some-erlang/mafiapp-1.0.0
diff options
context:
space:
mode:
Diffstat (limited to 'learn-you-some-erlang/mafiapp-1.0.0')
-rw-r--r--learn-you-some-erlang/mafiapp-1.0.0/Emakefile2
-rw-r--r--learn-you-some-erlang/mafiapp-1.0.0/ebin/mafiapp.app5
-rw-r--r--learn-you-some-erlang/mafiapp-1.0.0/logs/.keep-this0
-rw-r--r--learn-you-some-erlang/mafiapp-1.0.0/mafiapp.spec3
-rw-r--r--learn-you-some-erlang/mafiapp-1.0.0/src/mafiapp.erl172
-rw-r--r--learn-you-some-erlang/mafiapp-1.0.0/src/mafiapp_sup.erl12
-rw-r--r--learn-you-some-erlang/mafiapp-1.0.0/test/mafiapp.spec3
-rw-r--r--learn-you-some-erlang/mafiapp-1.0.0/test/mafiapp_SUITE.erl128
8 files changed, 325 insertions, 0 deletions
diff --git a/learn-you-some-erlang/mafiapp-1.0.0/Emakefile b/learn-you-some-erlang/mafiapp-1.0.0/Emakefile
new file mode 100644
index 0000000..b203ea3
--- /dev/null
+++ b/learn-you-some-erlang/mafiapp-1.0.0/Emakefile
@@ -0,0 +1,2 @@
+{["src/*", "test/*"],
+ [{i,"include"}, {outdir, "ebin"}]}.
diff --git a/learn-you-some-erlang/mafiapp-1.0.0/ebin/mafiapp.app b/learn-you-some-erlang/mafiapp-1.0.0/ebin/mafiapp.app
new file mode 100644
index 0000000..972432f
--- /dev/null
+++ b/learn-you-some-erlang/mafiapp-1.0.0/ebin/mafiapp.app
@@ -0,0 +1,5 @@
+{application, mafiapp,
+ [{description, "Help the boss keep track of his friends"},
+ {vsn, "1.0.0"},
+ {modules, [mafiapp, mafiapp_sup]},
+ {applications, [stdlib, kernel, mnesia]}]}.
diff --git a/learn-you-some-erlang/mafiapp-1.0.0/logs/.keep-this b/learn-you-some-erlang/mafiapp-1.0.0/logs/.keep-this
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/learn-you-some-erlang/mafiapp-1.0.0/logs/.keep-this
diff --git a/learn-you-some-erlang/mafiapp-1.0.0/mafiapp.spec b/learn-you-some-erlang/mafiapp-1.0.0/mafiapp.spec
new file mode 100644
index 0000000..4953e67
--- /dev/null
+++ b/learn-you-some-erlang/mafiapp-1.0.0/mafiapp.spec
@@ -0,0 +1,3 @@
+{alias, root, "./test/"}.
+{logdir, "./logs/"}.
+{suites, root, all}.
diff --git a/learn-you-some-erlang/mafiapp-1.0.0/src/mafiapp.erl b/learn-you-some-erlang/mafiapp-1.0.0/src/mafiapp.erl
new file mode 100644
index 0000000..0e6a2b0
--- /dev/null
+++ b/learn-you-some-erlang/mafiapp-1.0.0/src/mafiapp.erl
@@ -0,0 +1,172 @@
+-module(mafiapp).
+-behaviour(application).
+-include_lib("stdlib/include/ms_transform.hrl").
+-export([start/2, stop/1]).
+-export([install/1]).
+-export([add_friend/4, friend_by_name/1, friend_by_expertise/1,
+ add_service/4, debts/1]).
+-export([add_enemy/2, find_enemy/1, enemy_killed/1]).
+
+
+-record(mafiapp_friends, {name,
+ contact=[],
+ info=[],
+ expertise}).
+-record(mafiapp_services, {from,
+ to,
+ date,
+ description}).
+-record(mafiapp_enemies, {name,
+ info=[]}).
+
+start(normal, []) ->
+ mnesia:wait_for_tables([mafiapp_friends,
+ mafiapp_services,
+ mafiapp_enemies], 5000),
+ mafiapp_sup:start_link().
+
+
+stop(_) -> ok.
+
+install(Nodes) ->
+ ok = mnesia:create_schema(Nodes),
+ rpc:multicall(Nodes, application, start, [mnesia]),
+ mnesia:create_table(mafiapp_friends,
+ [{attributes, record_info(fields, mafiapp_friends)},
+ {index, [#mafiapp_friends.expertise]},
+ {disc_copies, Nodes}]),
+ mnesia:create_table(mafiapp_services,
+ [{attributes, record_info(fields, mafiapp_services)},
+ {index, [#mafiapp_services.to]},
+ {disc_copies, Nodes},
+ {type, bag}]),
+ mnesia:create_table(mafiapp_enemies,
+ [{attributes, record_info(fields, mafiapp_enemies)},
+ {disc_copies, Nodes},
+ {local_content, true}]),
+ rpc:multicall(Nodes, application, stop, [mnesia]).
+
+add_friend(Name, Contact, Info, Expertise) ->
+ F = fun() ->
+ mnesia:write(#mafiapp_friends{name=Name,
+ contact=Contact,
+ info=Info,
+ expertise=Expertise})
+ end,
+ mnesia:activity(transaction, F).
+
+friend_by_name(Name) ->
+ F = fun() ->
+ case mnesia:read({mafiapp_friends, Name}) of
+ [#mafiapp_friends{contact=C, info=I, expertise=E}] ->
+ {Name,C,I,E,find_services(Name)};
+ [] ->
+ undefined
+ end
+ end,
+ mnesia:activity(transaction, F).
+
+friend_by_expertise(Expertise) ->
+ %% Alternative form:
+ %% MatchSpec = [{#mafiapp_friends{name = '$1',
+ %% contact = '$2',
+ %% info = '$3',
+ %% expertise = Expertise},
+ %% [],
+ %% [{{'$1','$2','$3',Expertise}}]}],
+ %% F = fun() ->
+ %% [{Name,C,I,E,find_services(Name)} ||
+ %% {Name,C,I,E} <- mnesia:select(mafiapp_friends, MatchSpec)]
+ %% end,
+ Pattern = #mafiapp_friends{_ = '_',
+ expertise = Expertise},
+ F = fun() ->
+ Res = mnesia:match_object(Pattern),
+ [{Name,C,I,Expertise,find_services(Name)} ||
+ #mafiapp_friends{name=Name,
+ contact=C,
+ info=I} <- Res]
+ end,
+ mnesia:activity(transaction, F).
+
+%% Adding validation is left to the reader
+add_service(From, To, Date, Description) ->
+ F = fun() ->
+ case mnesia:read({mafiapp_friends, From}) =:= [] orelse
+ mnesia:read({mafiapp_friends, To}) =:= [] of
+ true ->
+ {error, unknown_friend};
+ false ->
+ mnesia:write(#mafiapp_services{from=From,
+ to=To,
+ date=Date,
+ description=Description})
+ end
+ end,
+ mnesia:activity(transaction,F).
+
+debts(Name) ->
+ Match = ets:fun2ms(
+ fun(#mafiapp_services{from=From, to=To}) when From =:= Name ->
+ {To,-1};
+ (#mafiapp_services{from=From, to=To}) when To =:= Name ->
+ {From,1}
+ end),
+ F = fun() -> mnesia:select(mafiapp_services, Match) end,
+ Dict = lists:foldl(fun({Person,N}, Dict) ->
+ dict:update(Person, fun(X) -> X + N end, N, Dict)
+ end,
+ dict:new(),
+ mnesia:activity(transaction, F)),
+ lists:sort([{V,K} || {K,V} <- dict:to_list(Dict)]).
+%% The following implementation is a somewhat more efficient
+%% alternative on large databases, given it will use indexes
+%% rather than traversing the entire table.
+% F = fun() ->
+% Given = mnesia:read({mafiapp_services, Name}),
+% Received = mnesia:match_object(#mafiapp_services{to=Name, _='_'}),
+% {Given, Received}
+% end,
+% {Given, Received} = mnesia:activity(transaction, F),
+% Dict1 = lists:foldl(fun(#mafiapp_services{to=N}, Dict) ->
+% dict:update(N, fun(X) -> X - 1 end, -1, Dict)
+% end,
+% dict:new(),
+% Given),
+% Dict2 = lists:foldl(fun(#mafiapp_services{from=N}, Dict) ->
+% dict:update(N, fun(X) -> X + 1 end, 1, Dict)
+% end,
+% Dict1,
+% Received),
+% lists:sort([{V,K} || {K,V} <- dict:to_list(Dict2)]).
+
+add_enemy(Name, Info) ->
+ F = fun() -> mnesia:write(#mafiapp_enemies{name=Name, info=Info}) end,
+ mnesia:activity(transaction, F).
+
+find_enemy(Name) ->
+ F = fun() -> mnesia:read({mafiapp_enemies, Name}) end,
+ case mnesia:activity(transaction, F) of
+ [] -> undefined;
+ [#mafiapp_enemies{name=N, info=I}] -> {N,I}
+ end.
+
+enemy_killed(Name) ->
+ F = fun() -> mnesia:delete({mafiapp_enemies, Name}) end,
+ mnesia:activity(transaction, F).
+
+%%%%%%%%%%%%%%%
+%%% PRIVATE %%%
+%%%%%%%%%%%%%%%
+
+find_services(Name) ->
+ Match = ets:fun2ms(
+ fun(#mafiapp_services{from=From, to=To, date=D, description=Desc})
+ when From =:= Name ->
+ {to, To, D, Desc};
+ (#mafiapp_services{from=From, to=To, date=D, description=Desc})
+ when To =:= Name ->
+ {from, From, D, Desc}
+ end
+ ),
+ mnesia:select(mafiapp_services, Match).
diff --git a/learn-you-some-erlang/mafiapp-1.0.0/src/mafiapp_sup.erl b/learn-you-some-erlang/mafiapp-1.0.0/src/mafiapp_sup.erl
new file mode 100644
index 0000000..e31b221
--- /dev/null
+++ b/learn-you-some-erlang/mafiapp-1.0.0/src/mafiapp_sup.erl
@@ -0,0 +1,12 @@
+-module(mafiapp_sup).
+-behaviour(supervisor).
+-export([start_link/0]).
+-export([init/1]).
+
+start_link() ->
+ supervisor:start_link(?MODULE, []).
+
+%% This does absolutely nothing, only there to
+%% allow to wait for tables.
+init([]) ->
+ {ok, {{one_for_one, 1, 1}, []}}.
diff --git a/learn-you-some-erlang/mafiapp-1.0.0/test/mafiapp.spec b/learn-you-some-erlang/mafiapp-1.0.0/test/mafiapp.spec
new file mode 100644
index 0000000..4953e67
--- /dev/null
+++ b/learn-you-some-erlang/mafiapp-1.0.0/test/mafiapp.spec
@@ -0,0 +1,3 @@
+{alias, root, "./test/"}.
+{logdir, "./logs/"}.
+{suites, root, all}.
diff --git a/learn-you-some-erlang/mafiapp-1.0.0/test/mafiapp_SUITE.erl b/learn-you-some-erlang/mafiapp-1.0.0/test/mafiapp_SUITE.erl
new file mode 100644
index 0000000..6005d29
--- /dev/null
+++ b/learn-you-some-erlang/mafiapp-1.0.0/test/mafiapp_SUITE.erl
@@ -0,0 +1,128 @@
+-module(mafiapp_SUITE).
+-include_lib("common_test/include/ct.hrl").
+-export([init_per_suite/1, end_per_suite/1,
+ init_per_testcase/2, end_per_testcase/2,
+ all/0]).
+-export([add_service/1, friend_by_name/1, friend_by_expertise/1,
+ friend_with_services/1, accounts/1, enemies/1]).
+
+all() -> [add_service, friend_by_name, friend_by_expertise,
+ friend_with_services, accounts, enemies].
+
+init_per_suite(Config) ->
+ Priv = ?config(priv_dir, Config),
+ application:load(mnesia),
+ application:set_env(mnesia, dir, Priv),
+ application:load(mafiapp),
+ mafiapp:install([node()]),
+ application:start(mnesia),
+ application:start(mafiapp),
+ Config.
+
+end_per_suite(_Config) ->
+ application:stop(mnesia),
+ ok.
+
+init_per_testcase(add_service, Config) ->
+ Config;
+init_per_testcase(accounts, Config) ->
+ ok = mafiapp:add_friend("Consigliere", [], [you], consigliere),
+ Config;
+init_per_testcase(_, Config) ->
+ ok = mafiapp:add_friend("Don Corleone", [], [boss], boss),
+ Config.
+
+end_per_testcase(_, _Config) ->
+ ok.
+
+%% services can go both way: from a friend to the boss, or
+%% from the boss to a friend! A boss friend is required!
+add_service(_Config) ->
+ {error, unknown_friend} = mafiapp:add_service("from name",
+ "to name",
+ {1946,5,23},
+ "a fake service"),
+ ok = mafiapp:add_friend("Don Corleone", [], [boss], boss),
+ ok = mafiapp:add_friend("Alan Parsons",
+ [{twitter,"@ArtScienceSound"}],
+ [{born, {1948,12,20}},
+ musician, 'audio engineer',
+ producer, "has projects"],
+ mixing),
+ ok = mafiapp:add_service("Alan Parsons", "Don Corleone",
+ {1973,3,1},
+ "Helped release a Pink Floyd album").
+
+friend_by_name(_Config) ->
+ ok = mafiapp:add_friend("Pete Cityshend",
+ [{phone, "418-542-3000"},
+ {email, "quadrophonia@example.org"},
+ {other, "yell real loud"}],
+ [{born, {1945,5,19}},
+ musician, popular],
+ music),
+ {"Pete Cityshend",
+ _Contact, _Info, music,
+ _Services} = mafiapp:friend_by_name("Pete Cityshend"),
+ undefined = mafiapp:friend_by_name(make_ref()).
+
+friend_by_expertise(_Config) ->
+ ok = mafiapp:add_friend("A Red Panda",
+ [{location, "in a zoo"}],
+ [animal,cute],
+ climbing),
+ [{"A Red Panda",
+ _Contact, _Info, climbing,
+ _Services}] = mafiapp:friend_by_expertise(climbing),
+ [] = mafiapp:friend_by_expertise(make_ref()).
+
+friend_with_services(_Config) ->
+ ok = mafiapp:add_friend("Someone", [{other, "at the fruit stand"}],
+ [weird, mysterious], shadiness),
+ ok = mafiapp:add_service("Don Corleone", "Someone",
+ {1949,2,14}, "Increased business"),
+ ok = mafiapp:add_service("Someone", "Don Corleone",
+ {1949,12,25}, "Gave a Christmas gift"),
+ %% We don't care about the order. The test was made to fit
+ %% whatever the functions returned.
+ {"Someone",
+ _Contact, _Info, shadiness,
+ [{to, "Don Corleone", {1949,12,25}, "Gave a Christmas gift"},
+ {from, "Don Corleone", {1949,2,14}, "Increased business"}]} =
+ mafiapp:friend_by_name("Someone").
+
+%% It should be possible to find all people who owe us things.
+accounts(_Config) ->
+ ok = mafiapp:add_friend("Gill Bates", [{email, "ceo@macrohard.com"}],
+ [clever,rich], computers),
+ ok = mafiapp:add_service("Consigliere", "Gill Bates",
+ {1985,11,20}, "Bought 15 copies of software"),
+ ok = mafiapp:add_service("Gill Bates", "Consigliere",
+ {1986,8,17}, "Made computer faster"),
+ ok = mafiapp:add_friend("Pierre Gauthier", [{other, "city arena"}],
+ [{job, "sports team GM"}], sports),
+ ok = mafiapp:add_service("Pierre Gauthier", "Consigliere", {2009,6,30},
+ "Took on a huge, bad contract"),
+ ok = mafiapp:add_friend("Wayne Gretzky", [{other, "Canada"}],
+ [{born, {1961,1,26}}, "hockey legend"],
+ hockey),
+ ok = mafiapp:add_service("Consigliere", "Wayne Gretzky", {1964,1,26},
+ "Gave first pair of ice skates"),
+ %% Wayne Gretzky owes us something so the debt is negative
+ %% Gill Bates are equal
+ %% Gauthier is owed a service.
+ [{-1,"Wayne Gretzky"},
+ {0,"Gill Bates"},
+ {1,"Pierre Gauthier"}] = mafiapp:debts("Consigliere"),
+ [{1, "Consigliere"}] = mafiapp:debts("Wayne Gretzky").
+
+enemies(_Config) ->
+ undefined = mafiapp:find_enemy("Edward"),
+ ok = mafiapp:add_enemy("Edward", [{bio, "Vampire"},
+ {comment, "He sucks (blood)"}]),
+ {"Edward", [{bio, "Vampire"},
+ {comment, "He sucks (blood)"}]} =
+ mafiapp:find_enemy("Edward"),
+ ok = mafiapp:enemy_killed("Edward"),
+ undefined = mafiapp:find_enemy("Edward").
+