NoTimeForCaution's avatar

updateOrCreate refactor

I have a One to One (User - Settings) relationship and am trying to refactor the below code to remove the ugly if/else. The below code resides in Livewire which is irrelevent but I wanted to mention.

if(is_null($this->user->settings)) {
     $this->user->settings()->create($validated);
} else {
     $this->user->settings()->update($validated);
}

This no bueno:

Setting::updateOrCreate(
     ['user_id' => $this->user->id],
	 $validated
);

What am I missing? Seems simple. Been one of those days.....

0 likes
10 replies
Snapey's avatar
$this->user->settings()->updateOrCreate($validated);
NoTimeForCaution's avatar

@Snapey - that was the one.

EDIT: Disregard, I thought I tried that earlier. It's been a long day.

That creates but doesn't update.

The only solution I've found is the original if/else from above.....

NoTimeForCaution's avatar

The solution: don't use updateOrCreate for One to One relationships.

NoTimeForCaution's avatar

FWIW, the original code works. Both the if/else and the updateOrCreate and @snapey syntax.

The problem is Eloquent won't update based on foreign_key alone. It creates a new instance instead of updating the existing instance, which is what I need. I'm simply updating settings for a User. So each User hasOne Setting. Each Setting belongTo one User.

Each example from above was creating a new Setting instance for the User because the user_id foreign key on the Setting table was not implicitly set to unique. There was no validation check for uniqueness of user_id because the data is persisted the following sytax or similar:

$this->user->settings()->updateOrCreate($validated);

So if anyone stumbles on this with updateOrCreate issues, check for similar behavior. Hope this helps someone. Thanks @snapey and @sergiu17 for being part of the solution in the end.

Snapey's avatar

maybe your problem stems from incorrect naming of the relationship?

im assuming user has one setting and setting belongs to user, so your settings table should have a user_id column and the relationship name should be singular setting

NoTimeForCaution's avatar

@Snapey You got me there, 100%.

I tried to get cute. The settings table does have a user_id column. I did use settings() in the User model because there are multiple columns in the Setting table to update.

I did revert to the correct naming conventions and that still doesn't prevent updateOrCreate from trying to instantiate a new instance of Setting for the User instead of updating the existing one.

And because I implicitly set the user_id column to unique(), as expected I get:

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1' for key 'settings.settings_user_id_unique'
Beekpr7's avatar

@NoTimeForCaution

I'm facing the same problem today, and this solution works good, it will update on needed

$program->service()->updateOrCreate(['program_id' => $program->id], $data_service);

I tried to refacto but this one doesn't work

$program->service()->updateOrCreate($data_service);
Snapey's avatar

@Beekpr7 Another option is firstOrNew

$service = $program->service()->firstOrNew();
$service->fill($data_service)->save();
1 like
Beekpr7's avatar

@Snapey I take a look and i tought "firstOrNew()" was behind the firstOrCreate

But the "firstOrCreate" is call behind the scene, and this methode do not required any mandatory parameter.

The thing is, we must give an "attributes" parameter to updateOrCreate(), so when we write the code under, the methode will search a record related to the array we gave. That's why this version doesn't work

$program->service()->updateOrCreate($data_service);  //FAILED

I tried and this one work's good

$program->service()->updateOrCreate(array(), $data_service);

An improvement suggestion, if the attributes parameters can be null

 public function updateOrCreate(array $attributes = [], array $values = [])

With the use of named parameters in php 8 we can do

$program->service()->updateOrCreate(values:$data_service);

But this need more reflections

Please or to participate in this conversation.