Although this question is caused by the PaperTrail gem, it's also applicable to the Rails' ActiveRecord in general.
TL;DR:
How do I save/create the new ActiveRecord object without triggering the ActiveRecord lifecycle callbacks?
NTL;R:
When I reify the deleted object from PaperTrail::Version backup, it, essentially, initialises a new new AR object and assigns it the attribute values from the version data.
When I then want to restore (persist) that object in database, Rails runs all the normal callbacks it would run when creating a new record, and, specifically, after_create. If any of the callbacks change the object's state we end up with an object, that is not the same as it was in the Version.
For a very simplified example (a lot of other things are happening in these callbacks), there is a Payout model with a serialised protocol field. Every change to the record's state is logged in that field:
after_createadds "%{timestamp} - Payout requested"before_updateadds "%{timestamp} - Payout status changed from 'requested' to 'processing'"- another
before_updateadds "%{timestamp} - Payout status changed from 'processing' to 'complete'" before_destroyfinally adds "%{timestamp} - Payout deleted by user 'whodunnit'"
Later, when I restore the Payout record from Version, it contains all the protocol entries, AND then adds to the end of the protocol one more entry, saying "%{timestamp} - Payout requested".
...you can imagine the level of WTFs when the accountant sees this...
Now, one of the ways to mitigate this is to add an instance variable flag, and/or a bunch of checks in the callbacks, and so on... But when you need to do that in a few dozens of models, it quickly becomes a fugly mess.
Therefore, my question is: how do I save/restore the record from PaperTrail in exactly the same state that it was in when the PaperTrail version was created, without triggering the ActiveRecord lifecycle callbacks? (and without hacking the Rails' internals too much). Ideally, this will be a concern that is included in all necessary models by a single line of code, hence I'm looking for a universal-ish solution.
UPDATE: save's functionality is needed to save the associated nested records, thus "update_columns" and plain insert of raw values is not really an option. As a last resort - maybe, but only if there is absolutely no other way.
Why don't use raw SQL to
insertrecovered dataLet's assume some Payout with
payout_idwas deletedIt's also possible to skip or even reset callbacks, in this case instead of insert, use usual
saveThat's not exactly the answer to the question, but probably it's not good idea to use callbacks in general
Rails callbacks seem like a cool feature until it turns into spaghetti, especially if you have different flows for different business operations (for example, record update by admin, by moderator and by user can be different)
To avoid such things, it is worth using some wrappers for operations where you do things similar to callbacks. It can be some gems like dry-rb or trailblazer, it can be PORO
For example (pseudocode)
In this case, you will not pollute the models with callbacks and run your operations where needed
There will also be no problems when trying to recover data from papertrail