onairmarc's avatar

Concept Question -Multi-Generational Inheritance

Hi All,

I'm working on building a help desk application, and one of the things I need to do is build out a service catalog and map each service item to an assignment group. I would like to allow a service item to inherit the assignment group of its parent when the assignment_group_id field is null. This would allow for the child's assignment group to be changed in a previous generation.

Goal

The scenario is this:

  • Service Item 1 has an assignment group of General Support
  • Service Item 2 does not have an assignment group and therefore inherits from Service Item 1.
  • Service Item 3 does not have an assignment group and therefore defers to its parent to check if it has an assignment group. If no assignment group is found, it will continue up the generational tree until a value is found.
  • Service Item 4 has an assignment group of Advanced Support
  • Service Item 5 does not have an assignment group and defers to its parent. Because Service Item 4 has an assignment group directly assigned and not inherited, the returned assignment group would be Advanced Support.

In the above example, each service item is a child on the previous service item, and no items in this example are siblings.

What I'm Thinking

I'm thinking about Eager Loading the relationships, but I'm unsure how/if that accomplishes the goal of values propagating down the generational tree. I had also considered using the Parental package from Tighten, but that appears to be more along the lines of different sub-types in a model rather than propagating values from parents to children and grandchildren.

The Ask

I would be eternally grateful if anyone had knowledge or insight into how this might be accomplished! I don't have any shareable code yet as I'm still trying to wrap my head around this problem, but I'd be happy to share the solution once I have it.

0 likes
3 replies
LaryAI's avatar
Level 58

One possible solution to achieve multi-generational inheritance in this scenario is by using recursive relationships in your database schema. Here's an example of how you can implement it:

  1. Create a "service_items" table with the following columns:

    • id (primary key)
    • name
    • assignment_group_id
    • parent_id (foreign key referencing the id column of the same table)
  2. Define the relationships in your Eloquent models. Assuming you're using Laravel, you can define the relationships in your "ServiceItem" model as follows:

class ServiceItem extends Model
{
    public function parent()
    {
        return $this->belongsTo(ServiceItem::class, 'parent_id');
    }

    public function children()
    {
        return $this->hasMany(ServiceItem::class, 'parent_id');
    }

    public function assignmentGroup()
    {
        return $this->belongsTo(AssignmentGroup::class);
    }
}
  1. To retrieve the assignment group for a service item, you can define an accessor method in your "ServiceItem" model that checks if the assignment_group_id is null and recursively traverses up the generational tree until a non-null assignment group is found:
class ServiceItem extends Model
{
    // ...

    public function getAssignmentGroupAttribute()
    {
        if ($this->assignment_group_id !== null) {
            return $this->assignmentGroup;
        }

        if ($this->parent !== null) {
            return $this->parent->assignmentGroup;
        }

        return null;
    }
}
  1. Now, you can access the assignment group for a service item using the "assignmentGroup" attribute:
$serviceItem = ServiceItem::find(2);
$assignmentGroup = $serviceItem->assignmentGroup;

In this example, if the assignment_group_id of Service Item 2 is null, it will inherit the assignment group from its parent (Service Item 1). Similarly, Service Item 3 will inherit from its parent (Service Item 2), and so on.

Note: This solution assumes that you have an "assignment_groups" table with an "id" column as the primary key. You may need to adjust the code according to your specific database schema.

onairmarc's avatar

I beleive I've implemented @laryai's automated suggestion but I'm now getting and error when I dd(). Here is the code for the ServiceCatalogItem model. If anyone can point out what I'm doing wrong, I'd greatly appreciate it.

class ServiceCatalogItem extends Model
{
    use BelongsToTenant, SoftDeletes;

    public $fillable = [
        'name',
        'type',
        'task_assignment_group_id',
        'parent_id',
    ];

    protected $attributes = [];

    protected static function boot()
    {
        parent::boot();

        static::creating(function ($model) {
            $model->setTenantId();
        });
    }

    public function parent(): BelongsTo
    {
        return $this->belongsTo(ServiceCatalogItem::class, 'parent_id');
    }

    public function children(): HasMany
    {
        return $this->hasMany(ServiceCatalogItem::class, 'parent_id');
    }

    public function taskAssignmentGroup()
    {
        return $this->belongsTo(TaskAssignmentGroup::class);
    }

    public function getTaskAssignmentGroupAttribute()
    {
        if ($this->task_assignment_group_id !== null) {
            return $this->taskAssignmentGroup;
        }

        if ($this->parent !== null) {
            return $this->parent->taskAssignmentGroup;
        }

        return null;
    }

    protected function tenantId(): Attribute
    {
        return Attribute::make(
            set: fn(string $value) => request()->session()->get('tenant_id'),
        );
    }

    protected function setTenantId(): void
    {
        $this->attributes['tenant_id'] = request()->session()->get('tenant_id');
    }
}

The Error Exception is:

Undefined property: App\Models\Sc\ServiceCatalogItem::$taskAssignmentGroup

Specifically:

return $this->taskAssignmentGroup;
onairmarc's avatar
onairmarc
OP
Best Answer
Level 5

I figured out my issue. I was referencing the wrong class name as well as not returning the parent of the record. Below is the code that I arrived at. Hopefully this helps others as well.

class ServiceCatalogItem extends Model
{
    use BelongsToTenant, SoftDeletes;

    public $fillable = [
        'name',
        'type',
        'task_assignment_group_id',
        'parent_id',
    ];

    protected $attributes = [];

    protected static function boot()
    {
        parent::boot();

        static::creating(function ($model) {
            $model->setTenantId();
        });
    }

    public function parent(): BelongsTo
    {
        return $this->belongsTo(ServiceCatalogItem::class, 'parent_id');
    }

    public function children(): HasMany
    {
        return $this->hasMany(ServiceCatalogItem::class, 'parent_id');
    }

    public function taskAssignmentGroup(): BelongsTo
    {
        return $this->belongsTo(TaskAssignmentGroup::class);
    }

    public function getTaskAssignmentGroupAttribute()
    {
        if ($this->task_assignment_group_id !== null) {
            return $this->taskAssignmentGroup();
        }

        if ($this->parent !== null) {
            return $this->parent->taskAssignmentGroup()->getParent();
        }

        return null;
    }

    protected function tenantId(): Attribute
    {
        return Attribute::make(
            set: fn(string $value) => request()->session()->get('tenant_id'),
        );
    }

    protected function setTenantId(): void
    {
        $this->attributes['tenant_id'] = request()->session()->get('tenant_id');
    }
}

Please or to participate in this conversation.