Person timeline: normalize PaperTrail changesets across create/update/destroy at read time
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
調査の方向性
app/views/application/_papertrail_versions.html.erb と既存の person-timeline reader から始め、PaperTrail のバージョンがどのように読み込まれ、render されるかを追跡します。共有 normalizer をどこに置くべきかを決め、両方の reader でそれを経由するようにし、nil の actor を処理しながら whodunnit ユーザーを一括ロードし、create、update、destroy イベントをカバーする specs を追加します。
索引モデルが issue の本文から書いたものです。
説明
Context
We're versioning financial/membership/registration models with PaperTrail (subject_person_id meta) to build a person-scoped timeline. PaperTrail stores diffs asymmetrically by event, so a naive reader has to special-case events:
| event | object (full state) |
object_changes (diff) |
|---|---|---|
| create | nil | {field: [nil, new]} |
| update | prior state | {field: [old, new]} |
| destroy | final state | nil |
Destroy events never populate object_changes; the final state lives only in object.
Decision 1 — normalize changesets at read time
Do not force object_changes onto destroy at write time — it duplicates data already in object, is a semantic fiction ([value, nil]), fights PaperTrail defaults, and only half-fixes the asymmetry (create's object is still nil).
Instead, normalize at read time with a single helper that returns a uniform {field => [before, after]} for any event, deriving destroy's diff from the object snapshot. One tested place; stored data stays honest and minimal.
def timeline_changeset(version)
case version.event
when "destroy"
version.object_deserialized.transform_values { |v| [v, nil] }
else
version.changeset # create & update already give [before, after]
end
end
Decision 2 — actor (whodunnit) is read-time only, no schema change
whodunnit is native PaperTrail (the built-in column — t.string "whodunnit" in db/schema.rb — plus set_paper_trail_whodunnit / PaperTrail.request.whodunnit / version.whodunnit). It already holds the acting user's id, per row, so "changed by" is available straight from the versions table (distinct from subject_person_id, which is the subject).
- No label snapshot. The only strong case for freezing an actor label was data loss on user deletion — but users with history won't be deleted, so that's moot. A snapshot would also go stale on name changes.
- No new
user_idcolumn.whodunnitalready carries the id. - Just batched-preload users at read time to kill the current N+1 (
User.find_byper row in the partial):
ids = versions.map(&:whodunnit).compact.map(&:to_i)
users = User.where(id: ids).index_by(&:id)
# per row: users[version.whodunnit.to_i]
Wart: whodunnit is a String, not a typed FK, so no includes(:user) and a .to_i cast is needed. Only if that ergonomics gets in the way, add a typed user_id meta later — a nicety, not a correctness need.
Scope
- Decide where the normalizer lives (Version decorator / timeline PORO / helper)
- Route the new person-timeline reader through it
- Route the existing
app/views/application/_papertrail_versions.html.erbpartial through it (currently skips destroy diffs entirely:version.event != 'destroy'— should show "everything removed") - Replace the actor N+1 in that partial (
User.find_by(id: version.whodunnit)per row) with a batched preload - Handle nil
whodunnit(system / console / seed / rake / background-job actions) — display as "System"/"Unknown" and account for it in any actor filter - Spec create/update/destroy cases
Notes
- Serialization is YAML (no PaperTrail initializer → defaults).
object_changesis auto-enabled because theobject_changescolumn exists onversions.
- 主要言語
- Ruby
- スター
- 15
- フォーク
- 28
- 平均マージ
- 1日 12分
- マージ済み PR(30日)
- 262
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile または Docker Compose ファイルあり
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
rubyforgood/awbw のほかの issue
-
blocked
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
rubyforgood/awbw#2824 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
rubyforgood/awbw#2822 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
rubyforgood/awbw#2160 ·
メンテナーはふだん 1 日以内に返信
-
Update callout faq defualt text when the new person subscription launches対応中かも @maebeale が 79 日前に担当しました。 オープンblocked
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
rubyforgood/awbw#2095 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
rubyforgood/awbw#2088 ·
メンテナーはふだん 1 日以内に返信
rubyforgood/awbw の issue をすべて見る
似ている issue
-
verified
難易度 2/5 1〜3時間 初心者へのやさしさ 67/100
メンテナーはふだん 5 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 86/100
akitaonrails/ai-book-2026#10 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
TheOdinProject/curriculum#31495 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
zerocracy/judges-action#2984 ·
メンテナーはふだん 1 日以内に返信