-1

Is it possible to have a models such as Cars and Overrides where I retain the original record in Cars, but substitute any update in Overrides (signified by col/val in overrides table).

Ideally this would be global anytime the record is retrieved.

So upon record entry to cars it would be:

id color make model
1 blue ford f150

But after creating an override:

The original record would remain car:blue,ford,f150 in cars but any columns that have a corresponding override are always displayed out when the record is fetched.

Immutable original record in cars

id color make model
1 blue ford f150

Its overrides:

id model_id column_override value_override
1 1 color red
2 1 make chevy

Virtually retrieved as:

id color make model
1 red chevy f150

Essentially

  • Keeping the original record the same.
  • Recording the column level change history.
  • But always showing the record with the latest override when requested via Laravel.

(ie). no changes show in cars. This would be different than Auditing since you would always want the overrides to show up.

medilies
  • 1,811
  • 1
  • 8
  • 32
john_ch
  • 103
  • 9
  • Why dont you use a table that store a **copy of the car before the update**. `CarOldState:id,car_id,color,make,model` then define **two relationships** `hasMany-oldState()` and `hasOne-oldStateLatest()` – medilies Feb 25 '22 at 22:20
  • 1
    Thx, thats a valid way but I should have mentioned that the original record will also receive continual updates from a data sync. So the underlying original will keep getting updates and those will be shown to the user unless the user has done a column override. So model may change every minute from sync, for ex: f150 might become f151, 152,..f999 via sync and will show that value unless the user overrides it at any time, but would still need the underlying to retain and keep getting updated via sync. – john_ch Feb 25 '22 at 22:25
  • Does this edit https://ibb.co/h1MF6P7 explain well your case ? – medilies Feb 26 '22 at 13:07
  • 1
    YES - the original immutable record is kept as-is (and updated via sync randomly) and anytime model is retrieved it has the overrides without needing to call a scope/relation/etc. the overrides are virtually retrieved "magically" - ideal this way so that its global and no code is needed anywhere the model is retrieved! – john_ch Feb 26 '22 at 20:05
  • For retrieved magically you can do that with **Eloquent getters**. While for setters you add a method to the model maybe called `fakeUpdate()` and call to add rows in `Overrides`. I submitted an edit to your question, I guess that communicate better your case. Tell me if you want me to writte an answer with what I discussed here – medilies Feb 26 '22 at 20:25
  • 1
    Yes that would help - I have the writing the overrides to db down using Model event Saving() but for retrieving, I can't find the correct place to put the logic as the data comes from 2 tables (cars & overrides) and has to be merged with logic - 1) pull all cars from db table 2) pull all overrides and then check by override_id to car id, if any columns have updates and if so, transform the returned collection to include this without any hardcoding of columns such as getColorAttribute() since the columns I provided were only a sample, the real table will have ~100 columns. – john_ch Feb 28 '22 at 14:46

1 Answers1

0

Even though we discussed that you prefer to make some of Laravel's magic happen, I'll try to make my suggested approche clearer.

The foundation of the solution

Tables structure

cars table

id color ... make model created_at

overrides table

car_id id color ... make model created_at

Eloquent relationships

class Cars extends Model{
  // ...

  public function overrides()
  {
    return $this->hasMany(Overrides::class);
  }

  public function latestOverride()
  {
    return $this->hasOne(Overrides::class)->latest();
  }

  // ...
}

A use case scenario

  • First care insertion
id color make model created_at
1 blue ford f150 now()

The 1st car row from now on is immutable, no code will be made to change it.

car_id id color make model created_at
  • First override is the user changing the color to red
car_id id color make model created_at
1 1 red ford f150 now()
  • Second override is the user changing the color to brown and make to chevy
car_id id color make model created_at
1 1 red ford f150 TIMESTAMP
1 2 brown ford chevy now()

Execution

Car creation

The creation of the Car is straight forward Car::create($data)

Overriding

public function storeOverride(StoreOverrideRequest $request ,Car $car){
  
  $latest_override = $car->latestOverride()->first();

  // Compare the request using "OR" with Override first (if the car have at least one override).
  // The Car also this request may be 'blue'. But the latest override is 'red'.
  // So this request count as a new different Override.

  // The following logic is applied to all properties
  // Stop the check and create at first diff
 if($request->color !== $latest_override->color || $request->color !== $car->color){
    Override::creat($request->validated());
  }
}

Retrieving

public function showCar(Car $car){
  return [
    'car' => $car,
    'latestOverride' => $car->latestOverride()->first(),
  ];
}

Benefits

  1. No hidden logic.
  2. Easy versionning. Your user can save overrides and navigate forward and backward through his history of changes.
  3. Guaranteed few exchanges with the database.

A column/value override approche will limit you on retrieval. you will be obliged to do something like $car->overrides and then loop through the Collection to replace the changes on $car.

Notes

  • Using Eloquent getters to query the latest value from the other model would result in a cumbersome code and a huge amount of fetching queries.
  • My point of view is that magic may get too complicated and hurt your code evolution and debugging (remember Doctor Strange causing the universe of madness XD)
medilies
  • 1,811
  • 1
  • 8
  • 32