i am calling the following code on my product object:
$data = App\products::findOrFail($id);
$inputs=request()->all();
$parts=array_values($inputs["parts"]);
$data->fill(["parts"=>$parts]);
the part being filled looks like this:
Array
(
[parts] => Array
(
[0] => Array
(
[id] => 1
[product_id] => 1
[display_name] => body
[formula_name] => body
[part_export_formula] =>
[precision_ID] => 12
[attributes] => Array
(
[1] => Array
(
[id] => 1
[parts_id] => 1
[attribute_name] => portion
[output_page_width] =>
[attribute_formula] => 0.6666
)
)
)
[1] => Array
(
[id] => 2
[product_id] => 1
[display_name] => formula
[formula_name] => formula
[part_export_formula] =>
[precision_ID] => 7
[attributes] => Array
(
[2] => Array
(
[id] => 2
[parts_id] => 2
[attribute_name] => formulation
[output_page_width] =>
[attribute_formula] => assembly2.variable*cabinet_width*test.density
)
)
)
[2] => Array
(
[id] =>
[product_id] => 1
[display_name] => foo
[formula_name] => foo
[part_export_formula] =>
[precision_ID] => 1
[attributes] => Array
(
[1566399634] => Array
(
[id] =>
[parts_id] =>
[attribute_name] => bar
[output_page_width] =>
[attribute_formula] => 10
)
)
)
)
)
the two relevant tables before the fill look like this:
partsAttributes:
+----+----------------+-------------------+-----------------------------------------------+--------+-----------------------+----------+---------------------+------------+------------+
| id | attribute_name | output_page_width | attribute_formula | is_qty | should_hide_on_output | parts_id | created_at | updated_at | deleted_at |
+----+----------------+-------------------+-----------------------------------------------+--------+-----------------------+----------+---------------------+------------+------------+
| 1 | portion | NULL | 0.6666 | 0 | 0 | 1 | 2019-07-17 18:11:19 | NULL | NULL |
| 2 | formulation | NULL | assembly2.variable*cabinet_width*test.density | 0 | 0 | 2 | 2019-07-17 18:11:19 | NULL | NULL |
+----+----------------+-------------------+-----------------------------------------------+--------+-----------------------+----------+---------------------+------------+------------+
parts:
+----+--------------+--------------+---------------------+--------------+---------------------+---------------------+------------+------------+
| id | display_name | formula_name | part_export_formula | precision_ID | created_at | updated_at | deleted_at | product_id |
+----+--------------+--------------+---------------------+--------------+---------------------+---------------------+------------+------------+
| 1 | body | body | NULL | 12 | 2019-08-21 16:24:08 | 2019-08-21 16:24:08 | NULL | 1 |
| 2 | formula | formula | NULL | 7 | 2019-08-21 16:24:08 | 2019-08-21 16:24:08 | NULL | 1 |
+----+--------------+--------------+---------------------+--------------+---------------------+---------------------+------------+------------+
and after like this:
partsAttributes:
+----+----------------+-------------------+-------------------+--------+-----------------------+----------+---------------------+---------------------+------------+
| id | attribute_name | output_page_width | attribute_formula | is_qty | should_hide_on_output | parts_id | created_at | updated_at | deleted_at |
+----+----------------+-------------------+-------------------+--------+-----------------------+----------+---------------------+---------------------+------------+
| 7 | bar | NULL | 10 | 0 | 0 | 7 | 2019-08-21 16:37:19 | 2019-08-21 16:37:19 | NULL |
+----+----------------+-------------------+-------------------+--------+-----------------------+----------+---------------------+---------------------+------------+
parts:
+----+--------------+--------------+---------------------+--------------+---------------------+---------------------+------------+------------+
| id | display_name | formula_name | part_export_formula | precision_ID | created_at | updated_at | deleted_at | product_id |
+----+--------------+--------------+---------------------+--------------+---------------------+---------------------+------------+------------+
| 1 | body | body | NULL | 12 | 2019-08-21 16:37:19 | 2019-08-21 16:37:19 | NULL | 1 |
| 2 | formula | formula | NULL | 7 | 2019-08-21 16:37:19 | 2019-08-21 16:37:19 | NULL | 1 |
| 7 | foo | foo | NULL | 1 | 2019-08-21 16:37:19 | 2019-08-21 16:37:19 | NULL | 1 |
+----+--------------+--------------+---------------------+--------------+---------------------+---------------------+------------+------------+
it deleted my existing attributes, i thought i might be the on delete cascade from the foreign key (maybe you were deleting the attribute, adding it back in, then deleting the part, adding it back in and the attribute was being deleted due to the cascade, but i removed the foreign key and this still happened. only the new attribute is left in the table)
i'm not sure what is happening here, but i'm pretty sure it is not the correct behaviour.
some more info from the sql log:
144 Prepare delete from `parts` where `parts`.`product_id` = ? and `parts`.`product_id` is not null
144 Execute delete from `parts` where `parts`.`product_id` = 1 and `parts`.`product_id` is not null
144 Close stmt
144 Prepare insert into `parts` (`id`, `product_id`, `display_name`, `formula_name`, `part_export_formula`, `precision_ID`, `updated_at`, `created_at`) values (?, ?, ?, ?, ?, ?, ?, ?)
144 Execute insert into `parts` (`id`, `product_id`, `display_name`, `formula_name`, `part_export_formula`, `precision_ID`, `updated_at`, `created_at`) values ('1', 1, 'body', 'body', NULL, '12', '2019-08-21 18:07:14', '2019-08-21 18:07:14')
144 Close stmt
144 Prepare delete from `partsattributes` where `partsattributes`.`parts_id` = ? and `partsattributes`.`parts_id` is not null
144 Execute delete from `partsattributes` where `partsattributes`.`parts_id` = 1 and `partsattributes`.`parts_id` is not null
144 Close stmt
144 Prepare update `partsattributes` set `id` = ?, `parts_id` = ?, `attribute_name` = ?, `output_page_width` = ?, `attribute_formula` = ?, `partsattributes`.`updated_at` = ? where `id` = ?
144 Execute update `partsattributes` set `id` = '1', `parts_id` = 1, `attribute_name` = 'portion', `output_page_width` = NULL, `attribute_formula` = '0.6666', `partsattributes`.`updated_at` = '2019-08-21 18:07:14' where `id` = 1
144 Close stmt
whatever it is you are doing the sql is deleting the attributes and then trying to update the deleted attributes instead of inserting a new one.
you can see the statement it makes for parts is different:
144 Prepare delete from `parts` where `parts`.`product_id` = ? and `parts`.`product_id` is not null
144 Execute delete from `parts` where `parts`.`product_id` = 1 and `parts`.`product_id` is not null
144 Close stmt
144 Prepare insert into `parts` (`id`, `product_id`, `display_name`, `formula_name`, `part_export_formula`, `precision_ID`, `updated_at`, `created_at`) values (?, ?, ?, ?, ?, ?, ?, ?)
144 Execute insert into `parts` (`id`, `product_id`, `display_name`, `formula_name`, `part_export_formula`, `precision_ID`, `updated_at`, `created_at`) values ('1', 1, 'body', 'body', NULL, '12', '2019-08-21 18:07:14', '2019-08-21 18:07:14')
144 Close stmt
in that case it inserts instead of updating, but for attributes it tries to update the existing ones it just deleted. i'll keep looking over the code to see why it's updating and not inserting (the delete and recreate is fine as long as it does parts then attributes which it seems to be doing, and insert is more correct here since the attribute it's trying to delete doesn't exist as it was deleted in the cascade, but it doesn't know that so it makes a kind of sense) i can tell where it does the delete, it stands out well enough, the update though...not so sure
ok, so after some work i have found if you change the line:
$related->exists = $related->getKey() != null;
to
$related->exists = $related->wasRecentlyCreated;
the problem goes away and it works fine.
what i think is happening is the line:
$related = $relation->getRelated()->newInstance($related);
is calling fill on the object, which then calls fillHasOneOrManyRelation on the child object, which then saves and creates the parent object in the process, the parent object needs to have exists set to true after that or it will try to insert again and get a pk violation, which i'm thinking is what you were doing here, however you were using the existence of a key as an indicator that the record exists, but the extant attributes also had a key, however you had deleted them in an earlier line so they weren't extant at that point.
so what i did was check if the record had recently been added (since you had previously deleted it the only way it can be in the database is if it had been recently inserted)
i'll clone down your repository, make the changes and put in a pull request