So Very, Very Lost in a Polymorphic Relationship

Published 1 week ago by jgravois

This is my very first attempt at a polymorphic relationship (I followed this lesson: [https://laracasts.com/series/how-do-i/episodes/8]) but I am quite lost.

I have users, equipment and tooling that can be “assigned” to a task so I created a trait for all three to inherit named Assignable.

I am trying to create an isAvailable() method that will return a boolean if the user, equipment or tooling is available during a specific period -- a call like return $user->isAvailable('2017-09-15 08:00', 2880); asking if a user can be assigned a task starting at 8:00 on 9/15 and lasting 2880 minutes. All I can seem to get is an exception of 'Object of class Illuminate\Database\Eloquent\Relations\MorphMany could not be converted to string'.

This is the schema of the table I am querying and the trait I created:

$table->increments('id');
$table->boolean('is_time_off')->default(false);
$table->boolean('was_notified')->default(false);
$table->integer('ws_task_id')->unsigned()->index();
$table->integer('assignable_id')->unsigned()->nullable();
$table->string('assignable_type')->nullable();
$table->dateTime('assign_date')->nullable();
$table->dateTime('assign_end_date')->nullable();
$table->timestamps();
<?php

namespace App\Traits;

use App\Models\AdcWorkscopeTask;
use App\Models\WsAssignedTask;
use Carbon\Carbon;

trait Assignable
{
    public function assignments()
    {
        return $this->morphMany(WsAssignedTask::class, 'assignable');
    } // end function

    public function scopeAssignedOn($query, AdcWorkscopeTask $task)
    {
        return $query->whereHas('assignments', function($query) use ($task) {
            $query->where('task_id', $task->id);
        });
    } // end function

    public function isAssignedOn(AdcWorkscopeTask $task)
    {
        return $this->assignments()->where('task_id', $task->id)->exists();
    } // end function

    public function isAvailable($date, $minutes)
    {
        //return $this->type;
        return $this->id;
    } // end function

    public function assign($task, $assignDate, $minutes)
    {
        $start_date = Carbon::createFromFormat('Y-m-d H:i', $assignDate);
        $end_date = $start_date->copy()->addMinutes($minutes);

        $this->assignments()->save(
            new WsAssignedTask([
                'ws_task_id' => $task->id,
                'is_time_off' => false,
                'was_notified' => false,
                'assign_date' => $start_date,
                'assign_end_date' => $end_date
            ])
        );
    } // end function

    public function park($assignDate, $minutes)
    {
        $start_date = Carbon::createFromFormat('Y-m-d H:i', $assignDate);
        $end_date = $start_date->copy()->addMinutes($minutes);

        $this->assignments()->save(
            new WsAssignedTask([
                'ws_task_id' => ' ',
                'is_time_off' => true,
                'was_notified' => false,
                'assign_date' => $start_date,
                'assign_end_date' => $end_date
            ])
        );
    } // end function
} // end trait
Best Answer (As Selected By jgravois)
36864

@martinbean Far as I can tell, this is a many-to-many polymorphic relation, so having multiple users/equipment wouldn't be a problem.

As for the original problem,

All I can seem to get is an exception of 'Object of class Illuminate\Database\Eloquent\Relations\MorphMany could not be converted to string'.

Where exactly does this happen?

The method you want to implement doesn't sound all that complicated unless I'm missing something.

You only need to check two things:

  • Check that the assignable doesn't have any assignments starting during the new task
  • Check that the assignable doesn't have any assignments ending during the new task
public function isAvailaible($date, $minutes)
{
//assuming $date is a Carbon instance. If not, just build one here from the $date.
$date_end = $date->copy()->addMinutes($minutes)
    return $this->assignments()
                ->orWhereBetween('assign_date', [$date, $date_end])
                ->orWhereBetween('assign_end_date', [$date, $date_end])
                ->exists();

}

EDIT: forgot carbon instances are mutable.

martinbean

@jgravois Could you not have separate relationships for users, equipment, and tooling, rather than a polymorphic relationship?

I assume you’re modelling tasks of some description, each of which require a user/equipment/tooling combination to complete. Again, I assume equipment can’t be operated without a user, so it doesn’t make sense to shoe-horn them into a single relationship.

jgravois

That was where I began but as it became more complex, I reached for that polymorphic lesson.

There are TASKS that require TECHS of a certain skill level and possibly EQUIPMENT and TOOLING ... the TECHS have to be notified the morning of the TASK as well as have all their TASKS listed when they log in.

To me, it seemed that a polymorphic like Jeff described would put all tasks to be in a single table and would make later processing simpler.

martinbean

@jgravois Yeah, it still doesn’t feel like a polymorphic relationship to me. Especially if a task needs techs and equipment or tooling.

If it’s some sort of booking/work allocation system, then you could have task assignees, and each assignee is then allocated to an item of equipment or tooling station. You’ll then be able to query your tasks and see who’s using what to work on those tasks, and also calculate their availability easier.

jgravois

I appreciate your time and ideas .... thanks!

The equipment and tooling is assigned just like staff ... it's not that the staff is using the equipment, the TASK is using the equipment. So, for example, removing the vertical stabilizer requires a rudder lock, 3 Level 4+ Techs and 2 other Techs (no level required - just hands and arms ... LOL)

So since equipment, tooling and staff are assigned in the same manner, wouldn't a polymorphic be the right tool?

martinbean

@jgravois I might have misunderstood, but if you have a single polymorphic relationship, then how do you show a task that requires tools and a user?

jgravois

LOL, don''t assume you misunderstood anything ... I am positive any ignorance is totally on my part.

Anyway, if I understand your question, the assignments table lists each (and is assigned via the assign method on the trait ... here is an example of use (still just testing but this works

foreach($tasks as $task) {
    if($task->id == 61) {
        $user->park('2017-09-14 08:00', 4320);
        $user->assign($task, '2017-09-20 08:00', 300);
        $lift->assign($task, '2017-09-20 08:00', 300);
        $pallet->assign($task, '2017-09-20 08:00', 300);
    } // end if
} // end foreach
jgravois

BUT before I can do that ... I have to make sure that user and that forklift and that metal pallet are available during that time

36864
36864
1 week ago (17,040 XP)

@martinbean Far as I can tell, this is a many-to-many polymorphic relation, so having multiple users/equipment wouldn't be a problem.

As for the original problem,

All I can seem to get is an exception of 'Object of class Illuminate\Database\Eloquent\Relations\MorphMany could not be converted to string'.

Where exactly does this happen?

The method you want to implement doesn't sound all that complicated unless I'm missing something.

You only need to check two things:

  • Check that the assignable doesn't have any assignments starting during the new task
  • Check that the assignable doesn't have any assignments ending during the new task
public function isAvailaible($date, $minutes)
{
//assuming $date is a Carbon instance. If not, just build one here from the $date.
$date_end = $date->copy()->addMinutes($minutes)
    return $this->assignments()
                ->orWhereBetween('assign_date', [$date, $date_end])
                ->orWhereBetween('assign_end_date', [$date, $date_end])
                ->exists();

}

EDIT: forgot carbon instances are mutable.

jgravois

OK, I added this to the trait

public function isAvailable($date, $minutes)
    {
        $date = Carbon::createFromFormat('Y-m-d H:i', $date);
        $date_end = $date->copy()->addMinutes($minutes);
        return $this->assignments()
            ->orWhereBetween('assign_date', [$date, $date_end])
            ->orWhereBetween('assign_end_date', [$date, $date_end])
            ->exists();
    }

and I get

UnexpectedValueException in Response.php line 450: The Response content must be a string or object implementing __toString(), "boolean" given.

BUT I WANT AN EFFIN BOOLEAN .... arrggggg

jgravois

BTW, I am calling it like this

return $user->isAvailable('2017-09-15 08:00', 2880);
jgravois

never mind, the return in the call was screwing with the response

since the api to use will be

if($user->isAvailable('2017-09-15 08:00', 2880)) {
    $user->assign('2017-09-15 08:00', 2880);
}

this is PERFECT!

Thank you @martinbean and @36864

Sign In or create a forum account to participate in this discussion.