Dreamer's avatar

Get history of model with soft deletes

Hello!

I am trying to make a history of model saves so the previous versions can always be accessed... even with relations by another model. Its becoming a bit tricky and i hope you guys can help me out on logic and some other stuff.

For example. User creates an Act, adds some products to it and saves it. Then admin deletes couple of products or changes their values. Now, when user starts to process saved Act (when using regular delete, some products are missing or changed), he can see products he added (and can see notification that they are updated or removed) and can do whatever with this Act. But, he cannot add those deleted products to a new act.

Same thing with viewing old Acts that have long ago deleted products.

Also, this history trait should work with a lot other models too, so, basicly, almost everything has previous versions.

My logic was this: This needs to be a trait that i can add to different models. Make a model soft deletable and add a serial field to db table. When user updates model, it takes a serial id for that model and soft deletes the model. Then it creates a new one based on data and adds the same serial field value as soft deleted one has. So, all previous versions are soft deleted and the latest, active one, is not.

Then, whenever needed, i could take soft deleted models and display them or make a new model with deleted one's data (restoring previous save).

What you think of this logic?

The problem i encountered was retrieving the soft deleted values for certain model with relations like this:

$product = Product::with('history')->find(468);

This did not work (i figured as much as there are no foreign keys)

public function history() {
    $class = get_class($this);
        $this->HasMany($class,'serial','serial');
    }

This did work but i would like to not be forced to use history command every time, i would very much prefer so it would always be there.

$product = Product::find(468);
$history = $product->history(); //this should be done behind the scenes

public function history() {
        $serial = $this->serial;
        $table = $this->getTable();

        return DB::table($table)->where('serial',$serial)->where('deleted_at','!=','null')->get();
    }

How can i have model to always load history?

0 likes
8 replies
Curdal's avatar

Just add this to your model

   /**
     * The relations to eager load on every query.
     *
     * @var array
     */
    protected $with = ['history'];
Dreamer's avatar

@igorblumberg I checked out the package you suggested. While it is great one and i will be using it for one of my other websites, its not suitable for my current project. The main "flaw" is that it wont keep previous versions of, for example, products so all previous versions could be accessed anytime by relations (see example at original post). It just records changes to models. Great for community websites to display updates.

I wonder if there is another packages like that... maybe some other ones do what i need. Tried to find one but none of them do exactly what i need.

I probably have to create the one form ground up but at least i got some good ideas from these packages.

JarekTkaczyk's avatar

@Dreamer From what you said, I guess this will do the job for you: http://softonsofa.com/revisionable-a-quick-example/

It lets you get a snapshot of a model at given version along with the related models data at this point in time as well. For example:

  1. Create Brand ['name' => 'Apple']
  2. Create Product ['name' => 'notebook', 'brand_id' => 1]
  3. Change Brand -> ['name' => 'Mac']
  4. Change Product ['name' => 'laptop', 'brand_id' => 1]

Now, in the history of your Product you will see (when using presenter like in the linked example):

  1. SomeUser created Product at 2015-11-10 12:00 ['name' => 'notebook', 'brand' => 'Apple']
  2. SomeUser updated Product at 2015-11-10 15:00 ['name' => 'laptop', 'brand' => 'Mac']
Dreamer's avatar

@JarekTkaczyk Thank you for the reply. Unfortunately, this is not exactly what i was looking for.

Heres an example what i am trying to do.

Admin creates 2 products:

'bananas' '1€' 'apples' '2€'

Now, employee of admin creates an act and adds these 2 products to it to be sold to a client.

'bananas' '1€' '3pcs'

'apples' '2€' '4pcs'

the total cost for the deal is 11€ and employee sends this act to a client to be accepted. the products are now reserved for that act and are fixed for the whole duration of a deal. (products are tied to an act with foreign keys through pivot table)

While employee waits for response, the admin updates the price of bananas to 2 € and deletes apples product, since they sold last of them and will not import anymore (only reserved apples remained).

Now, if using packages suggested, when clients views the act, the price of bananas has changed and apples product is missing. What needs to happen is that employee needs to finish the deal with fixed products and when admin reviews the deal year from now, they would still see all of the products sold.

That is why the products need to be soft deleted by every update and the new model needs to be created with new prices. So all foreign relations stay intact with fixed product details.

That is why i was going for that idea i mentioned on original post: This needs to be a trait that i can add to different models. Make a model soft deletable and add a serial field to db table. When user updates model, it takes a serial id for that model and soft deletes the model. Then it creates a new one based on data and adds the same serial field value as soft deleted one has. So, all previous versions are soft deleted and the latest, active one, is not.

Maybe i am overthinking it... that is why i asked for another opinion on it.

JarekTkaczyk's avatar

@Dreamer Hmm, your example of an act and products is exactly what I described above. The only thing you need is to find a snapshot of the product at given point in time. That is the acts creation time.

The only thing that I'm not sure of now, is whether the presenter handles many-to-many mappings. With one-to-one it would be a cinch though, so you have a starting point.

This is the relevant code:

    protected function passThroughRevisionable(Revisionable $revisionable, $key)
    {
        // Determine whether the model existed at the time of revision.
        if ($revisionable->created_at > $this->created_at) {
            return;
        }

        $target = $revisionable->{$key};

        // If we are working with related revisionable model then
        // return its version at the time of current revision.
        if ($target instanceof Revisionable) {
            return ($target->revisionSnapshot($this->created_at)) ?: $target;
        }

        return $target;
    }

now I guess you will need to adjust mapping (passThrough property on the presenter) so it works for a collection of related models, and you're good to go.

Dreamer's avatar

@JarekTkaczyk Hmm... Well, its just very confusing to me cause i am just 4 months in to laravel. Maybe you could clarify? I dont see how this can preserve relations. Pleas speak plainly :)

Please or to participate in this conversation.