Repeater relations meta data not merged with existing and overwrites other meta data.

I have 2 forms, each have different fields that both save in the SAME meta json column on the relation. form1: meta.passport.number + meta.passport.expiry form2: meta.nationality + meta.date_of_birth. The data from each form is saved but does not merge with existing meta. I've tried using the handleRecordUpdate and mutateFormDataBeforeSave on the page but $data is empty.
Have tried getting existing data from $this->record and merging with $this->data but the relationship save happens before these functions are called. In src/Components/Repeater.php the $this->saveRelationshipsUsing( has the correct data all merged in $state but then it's lost a bit further down. Is this expected behavior? Perhaps a workaround solution is to create a separate json column for each form - but I have 12 forms each with some different meta. Feels a bit clunky to make separate columns. Any advice gratefully received. Ta.
1 Reply
acroninja
acroninja11mo ago
I've figured this out if anyone has the same issue. JSON column $data is not merged into existing array but overwritten. To fix my specific case I've added this to the Repeater:-
->mutateRelationshipDataBeforeSaveUsing(function (array $data, Model $record): array {
// Check if 'meta' exists in both $data and $record
if (isset($data['meta']) && isset($record->meta)) {
// Merge the 'meta' data from the record with the data
$data['meta'] = array_merge_recursive($record->meta, $data['meta']);
}
return $data;
})
->mutateRelationshipDataBeforeSaveUsing(function (array $data, Model $record): array {
// Check if 'meta' exists in both $data and $record
if (isset($data['meta']) && isset($record->meta)) {
// Merge the 'meta' data from the record with the data
$data['meta'] = array_merge_recursive($record->meta, $data['meta']);
}
return $data;
})
A generic method to apply to any json column could be like this:-
->mutateRelationshipDataBeforeSaveUsing(function (array $data, Model $record): array {
$jsonColumns = $this->getJsonColumns($record);
foreach ($data as $key => $value) {
// Check if the current key exists in both $data and $record and it's a JSON column
if (isset($record->$key) && is_array($value) && in_array($key, $jsonColumns)) {
$data[$key] = array_merge_recursive($record->$key, $data[$key]);
}
}
return $data;
})
->mutateRelationshipDataBeforeSaveUsing(function (array $data, Model $record): array {
$jsonColumns = $this->getJsonColumns($record);
foreach ($data as $key => $value) {
// Check if the current key exists in both $data and $record and it's a JSON column
if (isset($record->$key) && is_array($value) && in_array($key, $jsonColumns)) {
$data[$key] = array_merge_recursive($record->$key, $data[$key]);
}
}
return $data;
})
Then to discover JSON columns - Note this is not tested. Maybe it would be better to specify the JSON columns that should be merged manually in a config file as this would result in a new db call for every record in the repeater.
protected function getJsonColumns(Model $record) {
return collect(\DB::connection()->getDoctrineSchemaManager()->listTableColumns($record->getTable()))
->filter(function ($column) {
return $column->getType()->getName() === 'json';
})
->map(function ($column) {
return $column->getName();
})
->toArray();
}
protected function getJsonColumns(Model $record) {
return collect(\DB::connection()->getDoctrineSchemaManager()->listTableColumns($record->getTable()))
->filter(function ($column) {
return $column->getType()->getName() === 'json';
})
->map(function ($column) {
return $column->getName();
})
->toArray();
}