Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Recordables Laravel Package

bjthecod3r/laravel-recordables

Laravel Recordables adds simple audit-trail behavior to Eloquent models, helping you track changes, attribute updates, and model events with minimal setup so you can keep a clear history of who changed what and when.

View on GitHub
Deep Wiki
Context7

Getting Started

laravel-recordables gives an Eloquent model a change-history trail: instead of hand-rolling an audit table, you attach the package's trait/behaviour and let it persist a record whenever a tracked model's attributes change. Version 0.2.0 is an early-stage release (0.1.0 → 0.2.0), so treat the public API as still settling.

Minimal path to a first working record:

  1. composer require bjthecod3r/laravel-recordables
  2. Publish and run the package migration (and config file, if the service provider exposes one) so the records table exists — check vendor/bjthecod3r/laravel-recordables/ for the migration stub and config keys before assuming defaults.
  3. Add the package trait to the model you want to audit.
  4. Call the recording method (recordIfChanged, per the 0.2.0 changelog) from the appropriate lifecycle hook — typically saved/updated, or an observer.
  5. Save the model, change an attribute, save again, and inspect the records table for one row per meaningful change.

Where to look first:

  • The config file — it usually decides the table name, the model class used for records, and whether recording is queued or synchronous.
  • The trait source — confirm which Eloquent events it hooks and whether it registers its own observer (boot{TraitName}), because that determines whether you need to call anything manually.
  • The migration — the column set tells you what the package can diff and query (old/new values, changed keys, morph columns, timestamps).

Your first real use case should be the boring one: a User or Post model with 2–3 scalar columns, proving the record is written once, with the correct before/after values. Get that right before adding casts, relations, or queues.

Implementation Patterns

Trait-on-model, record-on-save. The default pattern is a trait on the audited model plus a call in the saved (or updated) event. This gives you "one row per save that actually changed something" — which is exactly the behaviour recordIfChanged is named for and what 0.2.0 tightens up.

Observer for cross-model consistency. If you are auditing several models, prefer a shared base observer (or a small abstract model) over copy-pasting the trait call. It keeps the "what counts as changed" logic in one place when the package's comparison rules change — as they did in 0.2.0.

Record the diff, not the whole model. Store changed keys plus old/new values where the package supports it, rather than serialising the full attribute array. Full snapshots balloon fast on wide tables and make "what changed in this edit" queries painful.

Pair with a read-side model. Expose the records through a dedicated Eloquent model with a morphTo back to the audited record. Day-to-day, the useful queries are "history for this record, newest first" and "what did this user change last week" — make sure both indexes exist (morph columns, created_at).

Queue long writes if the package allows it. Audit rows are write amplification on every save. If the config exposes a queue/connection option, use it for high-traffic models; keep it synchronous for models where you need the record inside the same transaction.

Integrate at the boundary, not deep in the domain. Call the recording method from model events or a dedicated service — not sprinkled through controllers. That way saveQuietly(), imports, and console commands behave predictably.

Testing pattern. Use RefreshDatabase, mutate a model, assert a single record exists with the expected changed payload. Add an explicit regression test for cast attributes (see below) so a future package bump can't silently reintroduce duplication.

Gotchas and Tips

Casts are the sharp edge. The 0.2.0 fix — "Fix recordIfChanged recording duplicates for cast attributes" — is the single most important thing to know about this release. Eloquent's getDirty() / getOriginal() work on raw (pre-cast) values while $model->attribute returns the cast value; a naive comparison between the two sees a difference where there is none, so an unchanged save produces a spurious record. If you are on 0.1.0 and audit cast attributes (array, json, datetime, AsEnumCollection, custom casts), expect duplicate rows; upgrade, then re-verify existing data if duplicates already landed.

Re-saving without changes should be a no-op. After upgrading, add an assertion that $model->save() twice in a row yields exactly one record. If it yields two, something in your model (a mutator, an accessor with side effects, updated_at touching) is marking attributes dirty.

touch() and timestamp-only saves. touch() fires updated_at changes without real content changes. Decide deliberately whether that is a record worth keeping; usually it is not, so guard against it or use saveQuietly() in those paths.

saveQuietly(), bulk updates, and insert(). Model events don't fire for query-builder updates or bulk inserts, so those changes are invisible to the package. If you rely on the audit trail for compliance, route writes through models or add an explicit bulk-recording path.

Watch the transaction boundary. If recording is synchronous, a failing record write can roll back your domain save (or vice versa). Know which side you want to win; wrap deliberately in DB::transaction() rather than relying on default behaviour.

Pre-1.0 API churn. At 0.2.0 the package has no stability guarantee. Pin the version in composer.json, read the changelog before each bump, and keep the trait call isolated behind one observer/service so a signature change is a one-file edit.

Debugging duplicated or missing records. Log inside the model event: dump $model->getDirty(), $model->getChanges(), and $model->getCasts() for the attributes in question. That trio usually identifies cast mismatches immediately. Also confirm the trait's boot method is actually running (a typo'd trait name fails silently — no records, no error).

Extension points to prefer. Config-driven table/model names, a custom record model extending the package's, and overriding the "should record" predicate are the stable seams. Avoid overriding the diff-building internals, which is precisely the code 0.2.0 changed.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log