From 3999e736bee4b75504e13bde409d6b1a06e7f707 Mon Sep 17 00:00:00 2001
From: Jonathan Bernard <jdbernard@gmail.com>
Date: Wed, 15 Jun 2011 07:34:41 -0500
Subject: [PATCH] Bug fixes and implementation refinement around
 ``ts_ext_data``.

* Transformed the test database to match the new data model. Added
  ``ts_ext_data`` table and moved ``ts_user.ext_data`` values to it.
* Added D0022: cascade delete ``ts_ext_data`` when ``ts_entry`` record is
  deleted.
* Completed refactor of ``ts_api`` functions to account for extended data:

    * ``post_entry/3``
    * ``put_entry/3``

* Created ``ts_entry:write/2`` to write extended data atomically with the entry.
* Fixed copy/paste bug in ``ts_ext_data:create_table/1``.
* Fixed implementation of ``ts_ext_data:set_property/3`` to be explicit about
  taking a record, not a reference. It then extracts the reference and passes it
  to the underlying implementation in ``ts_ext_data:do_set_property/3``.
* Refactored ``ts_ext_data:do_set_property/3`` to take a record *reference*, not
  the record itself.
* Refactored the ``ts_ext_data:get_property/2`` and
  ``ts_ext_data:get_properties/1`` functions to return key value tuple pairs,
  not ``ts_ext_data{}`` records.
---
 db/test/DECISION_TAB.LOG       | Bin 156 -> 156 bytes
 db/test/LATEST.LOG             | Bin 92 -> 92 bytes
 db/test/schema.DAT             | Bin 8323 -> 8745 bytes
 db/test/ts_ext_data.DCD        | Bin 0 -> 304 bytes
 db/test/ts_ext_data.DCL        | Bin 0 -> 224 bytes
 db/test/ts_user.DCD            | Bin 258 -> 257 bytes
 doc/issues/desktop/0022bn5.rst |  10 ++++++++++
 src/ts_api.erl                 |   9 ++++-----
 src/ts_entry.erl               |  11 +++++++++--
 src/ts_ext_data.erl            |  27 +++++++++++++++------------
 10 files changed, 38 insertions(+), 19 deletions(-)
 create mode 100644 db/test/ts_ext_data.DCD
 create mode 100644 db/test/ts_ext_data.DCL
 create mode 100644 doc/issues/desktop/0022bn5.rst

diff --git a/db/test/DECISION_TAB.LOG b/db/test/DECISION_TAB.LOG
index c41385d58f11381a7db61f90c6a38d35d7bfc444..c329c24fc948a2e9ca6445005adcfd51b00385f3 100644
GIT binary patch
delta 20
ccmbQkIEQgU1p7<xBnIx~8z#nEGd;-y08B{-od5s;

delta 20
bcmbQkIEQgU1iMIY5(9IL_r!Q>rsG)vKJx}O

diff --git a/db/test/LATEST.LOG b/db/test/LATEST.LOG
index 761214661f0567a51a86cf6d6a8330757ac7f78c..c403a32f8bbfa94ca1373b9595972823ebcbd0d8 100644
GIT binary patch
delta 12
Tcma!vnGnkU(mRQPd&XP<9UcUG

delta 12
Tcma!vnGnh@(woG<{G$c{8D0b*

diff --git a/db/test/schema.DAT b/db/test/schema.DAT
index 0816fbdc60def99024f3c066ed5173c949baba3e..169aefd5cbda75eb7fd76aa644fe99cb0666a205 100644
GIT binary patch
delta 294
zcmZp6T<N00z@YGwVfESf3=9m6P|S&7GB5$f*g$xqhB_nL#Dft6?K(H)uIioD@7*hU
zhqbhD{$xkSrp;E29Q<sI8yOhnEj9!BjGIphGBUD$U|^61@r9T+KN5-L;QSzD5>^2;
z{KaM$S!qT_w#f-{=9AOK949}P(`00s%quSyCzHg$`1o2912<z$RuThCAUlx%tt|`a
z0uW%_2sL44a|Uw?16y%&Mrv+i3IlgZaeQh;NqkCTNn#FAoj?Xt3Ij`CVs0v`+~g|p
uYwS>klO4pw*_n${(^42%Cl|;#bAzpBN@C#Gc{_Q7nC50Td22={B~1W{*G$s@

delta 210
zcmZ4K((I_gz#wnTu=?zK1_lO3DCR^k8JK`#tROs5L!FUz;=u@k*r>oSt=y-2Iv4)D
z?GaecJ=u}5X|ok02mj^(#tz2Krvw=pS${Jy$XaZ6WE5iB{758{gY&nLNmvEY)X$q;
zWThDy*(WE+nRBNwaHLk0#HS>dBu=hUu$z2UPRE=%ks*UAg@HFOKQBKe)gdoGBfq#L
ogE<N40Er|9mO%C-2F7n~SwIH~Fhd;81@wT$=9da~j7$p60Oqwd(f|Me

diff --git a/db/test/ts_ext_data.DCD b/db/test/ts_ext_data.DCD
new file mode 100644
index 0000000000000000000000000000000000000000..3e52c5681e65e5a4ee3a3a2f6fedcf02a97c26c0
GIT binary patch
literal 304
zcma*hO-chX7{>9ZGqtu310KMICm3ug-3hu<{8+hAC^aPWzBFT|c_oRcYY*X7Jc(!U
z7zT9VLc#6BpC8YlD2MIMWd-2OTwff&*b!l;m$kC2i34GqT2gs%D5uSV!)V^jo9T>D
zd+|bi<i$!%wlu)jp26Vb)u4KN&*J%)SQ4rf6+b0KI%!+j!eMephJ)9E9EOh)9ATGz
z3eUM~`-j+xj}G;Tbbh_RJhdy?a3&A`Ab*0tO5@w*Ra_;x&&c9mNZsq0RO&X|cb>mr
FegZ%LUX}m=

literal 0
HcmV?d00001

diff --git a/db/test/ts_ext_data.DCL b/db/test/ts_ext_data.DCL
new file mode 100644
index 0000000000000000000000000000000000000000..9ea29b70d9d1910f34e9974f81bdfa7d3847d0d7
GIT binary patch
literal 224
zcmb7-OA5j;6h&_v>#qndz%>*rU4Tygj2tM4hNdr=en=#Vs1qmd!~MAn3$DQFJ)Coc
zkaddvCI+w+yY=Fwx`bh4&Rofyq$TvIXm~r+U}-X~(an-9NoRzSHrkM!Yom<wiU}Z^
z2n3&%K>X%y@%bw<6%u0axV(E#xzANtV^EQhR_B%oW2wD;w76)lt~A;y_NUOaZ$XXz
Q>9BQ2Z!Q%fy4ljpFV=rU^8f$<

literal 0
HcmV?d00001

diff --git a/db/test/ts_user.DCD b/db/test/ts_user.DCD
index 05cef8295eba6515ed9ce6547b1a8ba8a91733ab..72c0ed1ab890ca538f7cce159c14fd615f2406d1 100644
GIT binary patch
delta 32
ncmZo-YGj(=FD8@3!1&xCiGjP+i-Cb*ep0xPV{-=k#MC(eiqQ!u

delta 34
pcmZo<YGRt;FD8-1z!J%r#K6VBjDdk+K~lJnV{-<_#MC*ASpbWR2+RNg

diff --git a/doc/issues/desktop/0022bn5.rst b/doc/issues/desktop/0022bn5.rst
new file mode 100644
index 0000000..cecafa7
--- /dev/null
+++ b/doc/issues/desktop/0022bn5.rst
@@ -0,0 +1,10 @@
+Deleting an entry should cascade delete extended data.
+======================================================
+
+Currently the data remains in the database. It should not cause any 
+problems, but it is wasting space.
+
+=========  ==========
+Created:   2011-06-15
+Resolved:  YYYY-MM-DD
+=========  ==========
\ No newline at end of file
diff --git a/src/ts_api.erl b/src/ts_api.erl
index 122f015..42abb31 100644
--- a/src/ts_api.erl
+++ b/src/ts_api.erl
@@ -418,11 +418,10 @@ post_entry(YArg, Username, TimelineId) ->
             #ts_entry{ref = {Username, TimelineId, undefined}}, EJSON)
          catch _:InputError ->
             error_logger:error_report("Bad input: ~p", [InputError]),
-            throw(make_json_400(YArg))
+            throw(make_json_400(YArg, {request_error, InputError}))
          end,
 
-    %% TODO; should entries and their properties be created atomically?
-    case ts_entry:new(ER) of
+    case ts_entry:new(ER, ExtData) of
         % record created
         {ok, CreatedRecord} ->
             
@@ -445,14 +444,14 @@ put_entry(YArg, Username, TimelineId, EntryId) ->
     EJSON = parse_json_body(YArg),
 
     % parse into ts_entry record
-    ER = try ts_json:ejson_to_record_strict(
+    {ER, ExtData} = try ts_json:ejson_to_record_strict(
             #ts_entry{ref={Username, TimelineId, EntryId}}, EJSON)
          catch _:InputError ->
             error_logger:error_report("Bad input: ~p", [InputError]),
             throw(make_json_400(YArg))
          end,
 
-    ts_entry:write(ER),
+    ts_entry:write(ER, ExtData),
     make_json_200(YArg, ER).
 
 delete_entry(YArg, Username, TimelineId, EntryId) -> 
diff --git a/src/ts_entry.erl b/src/ts_entry.erl
index 3662f08..266db8a 100644
--- a/src/ts_entry.erl
+++ b/src/ts_entry.erl
@@ -1,6 +1,6 @@
 -module(ts_entry).
--export([create_table/1, new/1, new/2, update/1, update/2, write/1, delete/1,
-         lookup/3, list_asc/3, list_desc/3]).
+-export([create_table/1, new/1, new/2, update/1, update/2, write/1, write/2,
+         delete/1, lookup/3, list_asc/3, list_desc/3]).
 
 -include("ts_db_records.hrl").
 -include_lib("stdlib/include/qlc.hrl").
@@ -35,6 +35,13 @@ update(ER = #ts_entry{}, ExtData) when is_list(ExtData) ->
     
 write(ER = #ts_entry{}) -> mnesia:dirty_write(ER).
 
+write(ER = #ts_entry{}, ExtData) ->
+    {atomic, Result} = mnesia:transcation(fun() ->
+        ok = mnesia:write(ER),
+        ok = ts_common:do_set_ext_data(ER, ExtData)
+    end),
+    Result.
+
 lookup(Username, TimelineId, EntryId) ->
     case mnesia:dirty_read(ts_entry, {Username, TimelineId, EntryId}) of
         [] -> no_record;
diff --git a/src/ts_ext_data.erl b/src/ts_ext_data.erl
index c6406ef..2cb8fcd 100644
--- a/src/ts_ext_data.erl
+++ b/src/ts_ext_data.erl
@@ -4,41 +4,44 @@
 -include("ts_db_records.hrl").
 
 create_table(TableOpts) ->
-    mnesia:create_table(ts_entry,
+    mnesia:create_table(ts_ext_data,
         TableOpts ++ [{attributes, record_info(fields, ts_ext_data)},
                       {type, ordered_set}]).
 
 % set last timeline
-set_property(Ref=#ts_user{}, last_timeline, LastTimelineId) ->
-    do_set_property(Ref, last_timeline, LastTimelineId);
+set_property(Rec=#ts_user{}, last_timeline, LastTimelineId) ->
+    do_set_property(Rec#ts_user.username, last_timeline, LastTimelineId);
 
 % Set exclusion_list for a User account
-set_property(Ref=#ts_user{}, entry_exclusions, ExclusionList) ->
-    do_set_property(Ref, entry_exclusions, string:join(ExclusionList, "|"));
+set_property(Rec=#ts_user{}, entry_exclusions, ExclusionList) ->
+    do_set_property(Rec#ts_user.username, entry_exclusions, ExclusionList);
 
 % Set exclusion_list for a Timeline entry
-set_property(Ref=#ts_timeline{}, entry_exclusions, ExclusionList) ->
-    do_set_property(Ref, entry_exclusions, string:join(ExclusionList, "|"));
+set_property(Rec=#ts_timeline{}, entry_exclusions, ExclusionList) ->
+    do_set_property(Rec#ts_timeline.ref, entry_exclusions, ExclusionList);
 
-set_property(Ref, Key, Value) ->
+set_property(Rec, Key, _Value) ->
     throw(io_lib:format("Property '~s' not available for a ~s record.",
-        [Key, element(1, Ref)])).
+        [Key, element(1, Rec)])).
 
 get_property(Ref, PropKey) ->
     {atomic, Result} = mnesia:transaction(fun() ->
         case mnesia:read(ts_ext_data, {Ref, PropKey}) of
             [] -> not_set;
-            [Property] -> Property
+            [Property] -> Property#ts_ext_data.value
         end
     end),
     Result.
 
-get_properties(Ref) ->
+get_properties(Rec) ->
+    Ref = element(2, Rec),
     {atomic, Result} = mnesia:transaction(fun() ->
         MatchHead = #ts_ext_data{ref = {Ref, '_'}, _='_'},
         mnesia:select(ts_ext_data, [{MatchHead, [], ['$_']}])
     end),
-    Result.
+    lists:map(fun(ExtData = #ts_ext_data{}) ->
+        {Ref, Key} = ExtData#ts_ext_data.ref,
+        {Key, ExtData#ts_ext_data.value} end, Result).
 
 do_set_property(Ref, PropKey, Val) ->
     {atomic, Result} = mnesia:transaction(fun() ->