How to use table builder to manage many-to-many relationship

Does anyone have any experience using the table builder package to create a table to manage a many-to-many relationship? I have a simple many-to-many relationship with one of my models, Profile, and another one, Attributes. I set up the table component to list the profile's attributes. I set up getTableQuery() to return `Profile::attributes()->getQuery(). This works great.... Then, I add the AttachAction to the table's header actions, and this is where things begin to fall apart:
protected function getTableHeaderActions(): array
{
return [
Tables\Actions\AttachAction::make()
->relationship($this->profile->attributes())
->inverseRelationshipName('profile')
->recordTitleAttribute('name'),
];
}
protected function getTableHeaderActions(): array
{
return [
Tables\Actions\AttachAction::make()
->relationship($this->profile->attributes())
->inverseRelationshipName('profile')
->recordTitleAttribute('name'),
];
}
Trying to attach an attribute starts out fine: button, modal, dropdown...and when you type something, the generated query works and correctly returns the right data, but then it displays the same (wrong) value for each attribute: 'attribute'. Does anyone have any experience/examples...? Any suggestions/guidance...? 🤔
15 Replies
Travis
TravisOP2y ago
Would help to use a pivot model...? Then, I could switch to the create action and it would create pivot models, I suppose.... 🤔
awcodes
awcodes2y ago
A pivot model would make it easier, but shouldn’t relationship() be a string instead of the actual relationship data.? I don’t have a lot of experience with the attach action, but it seems off to me that the relationship and the inverse are both based on profile.
Dan Harrin
Dan Harrin2y ago
i dont think ->relationship() on the AttachAction is required at all you just need to implement HasRelationshipTable alongside HasTable then define getInverseRelationshipName() and getRelationship() you can then remove getTableQuery() and we will do the rest for an example, check out the RelationManager class in the admin panel
Travis
TravisOP2y ago
When I supplied the name of the relationship method as a string, I got an error....it required I pass a variable of a certain type. I can't remember at the moment, but it was basically an Illuminate "relation" contract. Thx...I'll try those things.... OK. I implemented HasRelationshipTable (defining getInverseRelationshipName() and getRelationship()), dropped getTableQuery(), and simplified AttachAction::make(). It still works like before. It appears to select the expected attributes for the profile, but then it shows the same string, attribute, for each one that's listed.
Dan Harrin
Dan Harrin2y ago
are the pivot attributes in the withPivot() of the relationship definition
Travis
TravisOP2y ago
No. Here's the Profile class (trimmed a bit):
class Profile extends Model
{
public function attributes(): BelongsToMany
{
return $this->belongsToMany(Attribute::class);
}
}
class Profile extends Model
{
public function attributes(): BelongsToMany
{
return $this->belongsToMany(Attribute::class);
}
}
And here's the Attribute class (also trimmed a bit):
class Proficiency extends Model
{
public function profile(): BelongsToMany
{
return $this->belongsToMany(Profile::class);
}
}
class Proficiency extends Model
{
public function profile(): BelongsToMany
{
return $this->belongsToMany(Profile::class);
}
}
There are no additional fields (at the moment) on the pivot table. And, just to be clear, the table displays fine. It's when I click attach, enter some letters, and view the dropdown results that I see the right number of options, but all with the same label, 'attribute'. (I've managed to debug to see that it's performing the right query and getting the right results. It seems to be in AttachAction::getRecordSelect() in the call to mapWithKeys() that things fall apart.
Dan Harrin
Dan Harrin2y ago
try AttachAction::make()->recordTitleAttribute('attribute') if there is an attribute column on the attributes table
Travis
TravisOP2y ago
The attributes table consists of id, name, created_at, and updated_at...at the moment.
Dan Harrin
Dan Harrin2y ago
ok so ->recordTitleAttribute('name')
Travis
TravisOP2y ago
That's what I had initially. Let me give it another try. It may take a minute as I'm trying all sorts of things that I need to undo/unwind.... OK. I can't get it back to a working state now. It's trying to select name in the pivot table just to show the table for some reason. 😕 It's super simple. That is, there's hardly any "extra" code...but I'm missing something, so I will try to figure it out, getting it to work again.
Dan Harrin
Dan Harrin2y ago
please send the entire livewire component in its most simplest form that doesnt work dont include anything that is not strictly related to this issue
Travis
TravisOP2y ago
Oh....I tried to add ->withPivot(['name']) to the relation definition from earlier and left it there.... 🤦‍♂️ OK. Let me rewind....see where I am, and then I'll share the livewire component like you said...if still needed.
<?php

namespace App\Http\Livewire\Profile;

use App\Models\Profile;
use Filament\Tables;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Contracts\HasRelationshipTable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\View\View;
use Livewire\Component;

class AttributesTable extends Component implements HasTable, HasRelationshipTable
{
use InteractsWithTable;

public Profile $profile;

public function getRelationship(): Relation | Builder
{
return $this->profile->attributes();
}

public function getInverseRelationshipName(): string
{
return 'profile';
}

protected function getTableHeaderActions(): array
{
return [
Tables\Actions\AttachAction::make()
->recordTitleAttribute('name'),
];
}

protected function getTableColumns(): array
{
return [
Tables\Columns\TextColumn::make('name'),
];
}

public function render(): View
{
return view('livewire.profile.attributes-table');
}
}
<?php

namespace App\Http\Livewire\Profile;

use App\Models\Profile;
use Filament\Tables;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Contracts\HasRelationshipTable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\View\View;
use Livewire\Component;

class AttributesTable extends Component implements HasTable, HasRelationshipTable
{
use InteractsWithTable;

public Profile $profile;

public function getRelationship(): Relation | Builder
{
return $this->profile->attributes();
}

public function getInverseRelationshipName(): string
{
return 'profile';
}

protected function getTableHeaderActions(): array
{
return [
Tables\Actions\AttachAction::make()
->recordTitleAttribute('name'),
];
}

protected function getTableColumns(): array
{
return [
Tables\Columns\TextColumn::make('name'),
];
}

public function render(): View
{
return view('livewire.profile.attributes-table');
}
}
The Profile model has an attributes relation (belongs-to-many). The Attribute model has a profiles relation (belongs-to-many). The pivot table is a named attribute_profile and has an ID, a profile ID, an attribute ID, and timestamp fields. The Attribute has a name field. The table is to list a profile's attributes and allow for attaching/detaching (and maybe creating/deleting)....
Dan Harrin
Dan Harrin2y ago
getInverseRelationshipName() should be profiles not sure if that is what is causing it or not
Travis
TravisOP2y ago
It was profile and I changed it to profiles shortly after copy/pasting the code. It's since been corrected and I still get the same thing. It seems like it's somewhere in AttachAction::getRecordSelect() in the mapWithKeys() call. At this point, it has the correct/expected set of attributes, each with the correct ID and name. But, when Action::getRecordTitle() is called, it returns nothing when calling $this->getCustomRecordTitle() and so it returns the value from $this->getTableRecordTitle(), which is attribute in this case....for all attributes. But, I think I solved it....by adding ->recordTitle() to the attach action creation:
Tables\Actions\AttachAction::make()
->recordTitleAttribute('name')
->recordTitle(function ($record) {
return $record->name;
})
Tables\Actions\AttachAction::make()
->recordTitleAttribute('name')
->recordTitle(function ($record) {
return $record->name;
})
I would think this wouldn't/shouldn't be necessary. It works, but why wasn't calling recordTitleAttribute('name') enough...? 🤔
Dan Harrin
Dan Harrin2y ago
i dont know why that would be the case, weird
Want results from more Discord servers?
Add your server