laravel-activitylog icon indicating copy to clipboard operation
laravel-activitylog copied to clipboard

Unchanged columns logged when selecting a subset of columns

Open KKSzymanowski opened this issue 1 year ago • 5 comments

Describe the bug I have a table which has an order column. When I reorder the elements I update each model with the new order. Because the table has some large JSON columns I select only id and order columns for efficiency. I set the new order and save the model.

foreach (Faq::query()->get(['id', 'order']) as $faq) {
    $faq->order = $newOrder[$faq->id];
    $faq->save();
}

The diffs created in the activity_log table look like this:

{
   "old": {
      "order": 1,
      "answer": null,
      "question": null
   },
   "attributes": {
      "order": 2,
      "answer": "long JSON content",
      "question": "long JSON content"
   }
}

From what I see laravel-activitylog fetches the model with all columns from the database before creating a log entry which kind of defeats the purpose of my "select list optimization" and produces an incorrect diff.

Generally I want these large JSON columns to be logged in case a user changes them but in this case I only change the order column so I would like to see only the order column in the diff.

I have tried running these updates straight off the Eloquent builder:

Faq::query()->where('id', $id)->update(['order' => $newOrder[$id]]);

but in this case nothing is logged.

I have the following activitylog configuration for my models:

public function getActivitylogOptions(): LogOptions
{
    return LogOptions::defaults()
                     ->logAll()
                     ->logOnlyDirty()
                     ->logExcept([
                         'id',
                         'created_at',
                         'updated_at',
                     ]);
}

To Reproduce Select a subset of columns from the database, change these columns, save the model and see that other (not initially selected) columns are present in the diff.

Expected behavior Only columns that've actually been changed should be present in the diff.

Versions

  • PHP: 8.1
  • Database: MySQL 8
  • Laravel: 9.31.0
  • Package: 4.6.0

KKSzymanowski avatar Sep 28 '22 21:09 KKSzymanowski

In your model you can define it like bellow:

  protected $logAttributes = ['name'];

    public function getActivitylogOptions(): LogOptions
    {
        $loggableAttributes = array_intersect(array_keys($this->attributes), $this->logAttributes);
        return LogOptions::defaults()
            ->logOnly($loggableAttributes)
            ->logOnlyDirty()
            ->dontSubmitEmptyLogs();
    }

This worked for me even when we updating fields which are not in the select statement.

vishalw89 avatar Jan 18 '23 11:01 vishalw89

Hi @KKSzymanowski,

When you call Faq::query()->get(['id', 'order']) you are running selecting all the columns from the database and only using id and order. To fetch only id and order you should use Faq::select(['id', 'order'])->get() this will only fetch id and order from the database

n41tik avatar Mar 28 '23 12:03 n41tik

@n41tik not true:

DB::listen(function($q) {
    dump($q->sql);
});

Faq::query()->get(['id', 'order']);
"select `id`, `order` from `faqs` where `faqs`.`deleted_at` is null order by `order` asc" // routes/console.php:19

KKSzymanowski avatar Mar 28 '23 12:03 KKSzymanowski

Hello everyone,

I am presenting the same situation that @KKSzymanowski indicates, is there any way to resolve it?

I tried @vishalw89 idea but it didn't work for me.

Greetings,

junnysolano avatar Nov 08 '23 23:11 junnysolano

Hello everyone again,

Considering this part

$properties['attributes'] = static::logChanges(

            // if the current event is retrieved, get the model itself
            // else get the fresh default properties from database
            // as wouldn't be part of the saved model instance.
            $processingEvent == 'retrieved'
                ? $this
                : (
                    $this->exists
                        ? $this->fresh() ?? $this
                        : $this
                )
        );

I have done the following in the method that returns the options

public function getActivitylogOptions(): LogOptions
    {
        $this->exists = false;

        return LogOptions::defaults()
            ->logOnly([
                'name',
                'last_name'
            ])
            ->useLogName('Person')
            ->logOnlyDirty()
            ->dontSubmitEmptyLogs();
    }

Do you think this could affect the general functioning of the project?

junnysolano avatar Nov 08 '23 23:11 junnysolano