Laravel pivot table create association once, then update
Question
I have the following tables:
User:
userID
...
Lesson:
lessonID
...
Users_Lessons_Status (which acts as a pivot table and holds other information):
userID references User.userID
lessonID references Lessons.lessonID
latestSectionID
percentComplete
What I want to do is, for each user, for each lesson, there should be a row in the pivot table that tells how much the user has completed in that lesson and what their latest section ID was. That is, there should be a unique pair with userID
and lessonID
(primary keys?).
I have set up my models like so:
<?php
class User extends Eloquent implements UserInterface, RemindableInterface {
...
public function lessonStatuses()
{
return $this->belongsToMany('Lesson', 'users_lessons_status', 'lessonID', 'userID')->withPivot('latestSectionID', 'percentComplete');
}
}
<?
class Lesson extends Eloquent {
protected $table = 'lessons';
protected $primaryKey = 'lessonID';
public function userStatuses()
{
return $this->belongsToMany('User', 'users_lessons_status', 'userID', 'lessonID');
}
}
?>
My current route looks like this:
Route::post('dbm/users/setLatestSectionID', function() {
if(Auth::check()) {
$user = User::find(Input::get('userID'));
$lesson = Lesson::find(Input::get('lessonID'));
$us = $user->lessonStatuses();
$us->attach($lesson->lessonID,
["latestSectionID" => Input::get('latestSectionID'), "percentComplete" => Input::get('percentComplete')] );
}
});
This works, however, it creates a new row every time I update it for the same userID
and lessonID
, so the pair is no longer unique. Which methods should I use for this purpose? I tried both save()
, attach()
and push()
in the documentation but I'm not sure which one to use here.
Edit: to clarify, the resulting table should look something like this:
id|userID|lessonID|latestSectionID|percentComplete
1 1 1 X Y
2 1 2
3 1 3
4 2 1
5 3 1
6 3 2
....
Edit 2: Fixed the User->belongsToMany()
method and added the withPivot
call.
Solution
It seems like a bug, nevertheless you can do this:
...->sync([$id], false); // detaching set to false, so it will only insert new rows, skip existing and won't detach anything
edit: As said in comment - it will not work for you, as you want to set pivot data. So basically there is no method to do this at the moment, but something like this should do:
// belongsToMany.php
public function attachOrUpdate($id, array $attributes = array(), $touch = true)
{
if ($id instanceof Model) $id = $id->getKey();
if ( ! $this->allRelatedIds()->contains($id)) // getRelatedIds() in prior to v5.4
{
return $this->attach($id, $attributes, $touch);
}
else if ( ! empty($attributes))
{
return $this->updateExistingPivot($id, $attributes, $touch);
}
}
I'm gonna test it and if it passes, send a pull request to 4.1
OTHER TIPS
I faced this recently and fixed it in this way:
Use updateExistingPivot
first and check the result , if result is 1
it means there were row with the same userID
and lessonID
and it's been updated successfully, otherwise, if result is 0
it means there were no rows with this userID
and lessonID
, so you can attach it in order to create new row
$update_result = $us->updateExistingPivot($lesson->lessonID,
["latestSectionID" => Input::get('latestSectionID'), "percentComplete" => Input::get('percentComplete')] );
if($update_result == 0) {
$us->attach($lesson->lessonID,
["latestSectionID" => Input::get('latestSectionID'), "percentComplete" => Input::get('percentComplete')] );
}
You should be using updateExistingPivot()
.
Update your code to use
$us->updateExistingPivot($lesson->lessonID,
["latestSectionID" => Input::get('latestSectionID'), "percentComplete" => Input::get('percentComplete')], true );
The last parameter will update the timestamps for all related models. If not, you can remove or set to false.
If you want to attach only when a record doesn't exist, you could do something like this...
Route::post('dbm/users/setLatestSectionID', function() {
if(Auth::check()) {
$user = User::find(Input::get('userID'));
$lesson = [
"latestSectionID" => Input::get('latestSectionID'),
"percentComplete" => Input::get('percentComplete')
];
$num_lessons = $user->lessonStatuses()->where('id', Input::get('lessonID'))->count();
if($num_lessons == 0) {
$user->attach($lesson->lessonID, $lesson);
} else {
$user->updateExistingPivot($lesson->lessonID, $lesson);
}
}
});