Skip to content

Name history - #340

Open
vangberg wants to merge 43 commits into
mainfrom
name-history
Open

vangberg wants to merge 43 commits into
mainfrom
name-history

Conversation

@vangberg

@vangberg vangberg commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Closes #44

Ready for review. This ended up being more code than I anticipated, and I think I could keep changing things here and there, so I think it is better to move on and start reviewing. Don't hold back, I am very open to changing overall direction if we can make it simpler :)

Overview:

  1. Versioned concern tracks model changes as Version records.
  2. versioned_together groups attributes, so if one attribute changes, we record the current state of the whole group. Useful for e.g. etymology.
  3. Special handling added for rich texts.
  4. PublicHistory takes a Name record and fetches all relevant Version records for itself + associations and turns it into a format that is easy for the views to consume.
  5. New Current model is an ActiveSupport::CurrentAttributes, which allows us to set per-request globals, i.e. current user.
  6. I ditched the Unlinkable approach for the SoftDeletable concern with a default_scope. The #linked scope ended up creeping up in so many places that I think the soft-deletion approach is saner.

Tracking versions

This PR adds a Versioned concern. When included in a model, Version records are automatically created on create/update/delete, and a versions association is added to the model.

This is what an update to Name which changes proposed_in_id looks like:

>> p name.versions.last
#<Version:0x000000015401f158
 id: 20,
 record_type: "Name",
 record_id: 55876,
 operation: "update",
 changeset: {"proposed_in_id"=>[4482, 2667]},
 created_at: Fri, 02 Oct 2026 12:29:52.288092000 UTC +00:00>
irb(main):018> 

Tracking changes to etymology

A change to one etymology attribute should snapshot the current value of all etymology attributes ( + name which is used by full_etymology). This is done with versioned_together, which lets you specify which attributes should be tracked as a single attribute.

Public history

When rendering the history, we need to:

  1. Only show history for some attributes -- other attributes are internal.
  2. Group etymology changes together.
  3. Fetch the history for associations (placements, publications, rich texts).

This is all orchestrated by Name::PublicHistory.

UI

CleanShot-2026-10-02-15-36-10 CleanShot-2026-10-29-14-30-56

Value flow

Single attribute

Name#syllabication

nil → 'co.li'

↓ Version#changeset

{
  'syllabication' => [nil, 'co.li']
}

↓ PublicHistory#changes_for(version)

{
  'syllabication' => [nil, 'co.li']
}

Grouped etymology

Name#etymology_xx_description

'of the colon' → 'from the colon'

↓ Version#changeset — includes unchanged companions

{
  'name' => ['E. coli', 'E. coli'],
  'etymology_xx_lang' => ['N.L.', 'N.L.'],
  'etymology_xx_description' => ['of the colon', 'from the colon']
}

↓ PublicHistory#changes_for(version) — groups before/after snapshots

{
  'etymology' => [
    {
      'name' => 'E. coli',
      'etymology_xx_lang' => 'N.L.',
      'etymology_xx_description' => 'of the colon'
    },
    {
      'name' => 'E. coli',
      'etymology_xx_lang' => 'N.L.',
      'etymology_xx_description' => 'from the colon'
    }
  ]
}

@vangberg vangberg self-assigned this Oct 2, 2026
@vangberg
vangberg added this pull request to stack #342 October 8, 2026 07:18
Extend the public history to cover publication roles, name relationships,
the genome, the nomenclatural type and external identifiers. Group the
polymorphic type's ID and type so each side renders with its own class.

Resolve foreign keys through Name's non-polymorphic belongs_to
reflections rather than hard-coded attribute lists, and use the generic
history_record_reference for publications too.
Pair each polymorphic belongs_to's type and ID columns using its
reflection rather than naming nomenclatural_type, and resolve the
recorded type to any model class instead of a fixed allowlist.
Serve the history as a nested singular resource at the same URL, and move
its helpers and tests alongside it, leaving NamesHelper for displaying
names.
Nothing writes type_material and type_accession any more.
Drop tests that only exercise Rails behaviour (no-op saves, transaction
rollback, dependent deletes, default scopes, simple delegation), edge
cases (empty or Unicode diff input, timestamp-only changes, deleted or
unknown polymorphic types, untracked placement creation), and helper and
controller tests that repeat what the model tests already cover.
Show linking and unlinking a publication or placement only as a change
of 'linked', without the attributes it was added or changed with.

Handle the nomenclatural type explicitly instead of discovering
polymorphic associations by reflection, both when grouping its type and
ID and when rendering them.
Restore finding polymorphic associations by reflection in
history_attribute_values, consistent with how other associations are
matched there.
@vangberg
vangberg marked this pull request as ready for review October 29, 2026 19:05
@vangberg

vangberg commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

My biggest issue is SoftDeletable. Unlinkable was not any better. It only exists so we can fetch versions for e.g. PublicationName that has been deleted. One (opinionated) option would be to have all versions specify an 'owner'. In this case, all versions would have a Name as an owner and we could just do Version.where(owner: name).

Quick sketch of what that would look like: https://github.com/seq-code/registry/compare/name-history...name-history-version-owner?expand=1

Hmm.

@vangberg
vangberg requested a review from lmrodriguezr October 29, 2026 19:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Record name history events

1 participant