Skip to content

Writing a store

tilttek/writing-goals

An application keeps the goals and the counts in its own storage by implementing two contracts.

Contract\GoalStore

MethodDoes
save($goal)Keeps the goal, replacing the one of the same identifier
find($id)The goal, or null
goalsOf($author)The author's goals, by identifier
remove($id)Removes the goal; nothing when there is none

A goal is rebuilt with its constructor from its columns: the identifier, the author as ResourceReference::toString(), the target, the two days as Day::toString(), the unit's and curve shape's values, the custom curve's points, the writing days' weekdays and excluded(), and the timezone's name; Curve::of() reads a curve back from its shape and points.

Contract\LogStore

MethodDoes
progress($goal)The goal's counts, as a Progress — Progress::fromArray() reads them by date
add($goal, $day, $delta)Adds to a day's count in one step, never below 0; the day's count after
set($goal, $day, $count)Replaces a day's count
forget($goal)Removes every count of the goal

add() must not lose a count when two requests add at once. In SQL, one row per goal and day, and an upsert that adds:

CREATE TABLE writing_log (
    goal VARCHAR(128) NOT NULL,
    day DATE NOT NULL,
    count INTEGER NOT NULL,
    PRIMARY KEY (goal, day)
);

INSERT INTO writing_log (goal, day, count) VALUES (?, ?, LEAST(1000000000, GREATEST(0, ?)))
ON CONFLICT (goal, day) DO UPDATE SET count = LEAST(1000000000, GREATEST(0, writing_log.count + EXCLUDED.count)) RETURNING count;

The library's tests hold GoalStoreTestCase and LogStoreTestCase, the contracts every store passes; copy them into the application's tests and extend them with the stores to check.