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.

Was it helpful?

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);

        }
    }
});
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top