Recording Editorial Decisions
The current schema meets the functional requirements of HopPress for its initial release. Let’s see whether the non-functional requirements are met. Take the legal constraint from Chapter 1: the university must retain a record of every editorial decision. Stated as a requirement, HopPress must record who made each editorial decision, what the decision was, and when it was made.
Check the schema against it. editor_id says which editor is assigned to a post. published_at says when a post was published. That is all. The schema cannot say:
- when a post was submitted, or by whom
- when a post was returned, or what the editor said
- whether a post was returned more than once
One fix is to add columns to posts: submitted_at, returned_at, return_note. That records one submission and one return. When the author resubmits and the editor returns the post a second time, the new values overwrite the old ones, and the first decision is gone. The requirement asks for every decision, not the latest.
The solution is to store each decision in its own row in a new table, post_state_changes. Here is a schema for it:
post_state_changes
change_id integer primary key
post_id integer foreign key -> posts, not null
actor_id integer foreign key -> users, not null
from_state text null, check in ('draft', 'submitted', 'published', 'returned')
to_state text not null, check in ('draft', 'submitted', 'published', 'returned')
note text null
occurred_at timestamp not null
One row per transition. Creating a draft adds the first row for a post to post_state_changes, with from_state null. Submitting, publishing, and returning each add a row. actor_id is the user who made the change, whether author or editor. note holds the editor’s comment when a post is returned.
erDiagram
direction LR
posts ||--o{ post_state_changes : has
users ||--o{ post_state_changes : makes
Authors can delete drafts and returned posts, but deletion must preserve any editorial decisions already recorded. Each history row has a foreign key to posts, so we keep the post’s row too. To support this, we add a column to posts:
deleted_at timestamp null
The timestamp is null until the author deletes the post. Deleting a post sets that timestamp, and the application excludes deleted posts from its ordinary lists and pages and refuses further edits or submissions. This is called soft deletion. The post and its history remain stored for recordkeeping.
Soft deletion leaves the post’s last editorial state unchanged. It does not add a row to post_state_changes, which records creation and transitions between the four editorial states. deleted_at records when the author deleted the post.
The posts table still holds state, editor_id, and published_at. state and published_at can now be derived from post_state_changes. editor_id records the current assignment. Whether to keep the duplicated values is the next question.