Laravel Nova show Many-to-Many with pivot - laravel

I have two tables show_types and venues. With a many-to-many relationship i did create a new pivot as follows
public function up()
{
Schema::create('show_types_venues', function (Blueprint $table) {
$table->integer('show_types_id')->unsigned();
$table->foreign('show_types_id')->references('id')->on('show_types');
$table->integer('venue_id')->unsigned();
$table->foreign('venue_id')->references('id')->on('venues');
});
}
And added to the models
ShowType.php
public function venues()
{
return $this->belongsToMany(Venue::class, 'show_types_venues', 'show_types_id', 'venue_id');
}
Venue.php
public function shows()
{
return $this->belongsToMany(ShowType::class, 'show_types_venues', 'venue_id', 'show_types_id');
}
I'm trying to make this relation show up on Nova using
ShowType.php
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Name'),
BelongsToMany::make('Venues', 'venues', Venue::class)
];
But nothing shows up in my interface. How can i make it show a select multiple where I could add/edit the venues associated with this showType?
Do i need to create something more "manual" ? Thanks

Without luck with the documentation of Nova, I did a workaround using Bejacho's Belongs to Many Fields in which we can easily add the many to many relationships appearing as a multiple selector on Nova.
Having the same setup as i mentioned above the only thing i did was to follow their example:
use Benjacho\BelongsToManyField\BelongsToManyField;
public function fields(Request $request){
return [
..., //If you are using with BelongsToMany native Field, put this field after
BelongsToManyField::make('Role Label', 'roles', 'App\Nova\Role'),
];
}
And worked perfectly for what I needed!

Related

How can I paginate Laravel's Eloquent's relationships' relationships?

I have two tables, users and profiles. A user has one profile. Also a user has referrals. The referrals are referenced by the column referrer_id in the users' table. So a user has a referrer, and a user can have many referrals.
Define a one-to-one relationship on the User's Model:
public function profile()
{
return $this->hasOne(Profile::class);
}
Define an inverse one-to-one relationship on the User's Model:
public function referrer()
{
return $this->belongsTo(User::class);
}
Define a one-to-many relationship on the User's Model:
public function referrals()
{
return $this->hasMany(User::class, 'referrer_id');
}
Define an inverse one-to-one or many relationship on the Profile's Model:
public function user()
{
return $this->belongsTo(User::class);
}
I wish to retrieve the user's profile, the user's referrals along with their profiles, and the referrals' referrals along with a count of each of the referrals' referrals.
The following Eloquent query works, but doesn't paginate:
namespace App\Http\Controllers;
class ReferralsController extends Controller
{
public function index(Request $request)
{
return $request->user()->loadMissing(['profile', 'referrals' => function ($query) {
$query->with(['profile', 'referrals'])->withCount('referrals');
}]);
}
}
I've tried to add ->paginate() to the query (on both as show below and also one or the other) but it doesn't work:
return $request->user()->loadMissing(['profile', 'referrals' => function ($query) {
$query->with(['profile', 'referrals'])->withCount('referrals')->paginate(2);
}])->paginate(2);
Adding it to the inner function doesn't do anything, and adding it to the main query just retrieves the entire users table.
EDIT
I've realized that adding ->paginate() to the inner function actually does limit the number of rows in the collection, but there is no Paginator instance anywhere, so I don't have access to any of the links to move pages.
Got it to work by doing it separately:
$profile = $request->user()->profile()
->firstOrFail();
$referralsPaginator = $request->user()->referrals()
->with('profile')
->withCount('referrals')
->paginate(2);

Define additional relationship on many-to-many polymorphic relationship

I'm creating an taggable table like so:
Schema::create('taggable', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tag_id');
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
$table->unsignedBigInteger('taggable_id');
$table->string('taggable_type');
$table->unsignedBigInteger('company_id');
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
$table->unsignedBigInteger('created_by')->nullable();
$table->foreign('created_by')->references('id')->on('users')->onDelete('set null');
$table->timestamps();
});
As you can see, next to connecting tags to a Post, Video etc (as per the Laravel docs example), I'd also like to ensure that the row that's added is connected to a Company and User model so I can keep track who it belongs to and who created it, but even more so access properties from those models in controllers and views.
I know that in my Post model I can do:
public function tags()
{
return $this->morphedByMany(\App\Models\Tag::class, 'taggable')->withPivot('created_by', 'company_id', 'created_at');
}
The problem is that this will retrieve just the value for created_by and company_id and not the Eloquent model. Is this possible?
So what I'd like to do is access properties of those relationships in controllers and views like so:
$post = Post::findOrFail(1);
foreach($post->tags as $tag) {
$tag->created_by->name // accessing 'name' property on the `User` model
}
foreach($post->tags as $tag) {
$tag->company->address // accessing `address` property on the `Company` model
}
You must do like below:
first you must define relationship between tags and users
class Tags extends Model
{
public function taggable(): MorphTo
{
return $this->morphTo();
}
public function createdBy(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
}
then for achieve that you want you must:
$post = Post::first();
$users = $post->tags()->with('createdBy')->get();

Use pivot table for extra attributes?

I am following the many to many relationships Laravel tutorial here on Laracasts - https://laracasts.com/series/laravel-5-fundamentals/episodes/21
My challenge to myself is, I have created the pivot table article_tag) to keep track of the many to many relations. Articles can have many tags, and tags can have many articles. So I can runsyncetc to associatetagXtagYtagZtoarticle1`. However I also want to be able to optionally set one of the associated tags as "isGreenTag". Can I do this within the pivot table that is keeping track of the many-to-many relations? Could I add a column "is_green_tag"?
Here is my Article class relationship:
class Article extends Model {
public function tags() {
return $this->belongsToMany('App\Tag')->withTimestamps();
}
}
Here is my Tag class relationship:
class Tag extends Model {
public function articles() {
return $this->belongsToMany('App\Articles');
}
}
Here is my migration for the pivot table:
public function up() {
Schema.create('article_tag', function(Blueprint $table) {
$table->integer('article_id')->unsigned()->index();
$table->foreign('article_id')->references('id')->on('articles')->onDelete('cascade');
$table->integer('tag_id')->unsigned()->index();
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
$table->timestamps();
});
}
Can I add to the pivot table migration $table->boolean('is_green_tag')->nullable()?
Yes you can, you can give it a default of 0 instead of making it nullable:
$table->boolean('is_green_tag')->default(0);
And then you can modify the relationship on Article class:
public function tags() {
return $this->belongsToMany('App\Tag')->withPivot(['is_green_tag'])->withTimestamps();
}
Once you have an Article object, you can access that property:
foreach ($article->tags as $tag) {
if ($tag->pivot->is_green_tag) {
// some logic here
}
}
To save is_green_tag for a $tagId:
$article->tags()->attach($tagId, ['is_green_tag' => 1]);
Laravel docs:
https://laravel.com/docs/5.7/eloquent-relationships#many-to-many
https://laravel.com/docs/5.7/eloquent-relationships#updating-many-to-many-relationships

How to get specific columns in Laravel Eloquent with pagination?

I use this table schema:
Schema::create('forms', function (Blueprint $table) {
$table->increments('id');
$table->string('name', 255)->default('');
$table->text('html')->nullable();
$table->text('json')->nullable();
$table->timestamps();
$table->softDeletes();
});
This is the model:
class Form extends Model
{
use SoftDeletes;
protected $fillable = [
'name',
'html',
'json'
];
protected $hidden = ['created_at', 'updated_at', 'deleted_at'];
}
And in the controller I want to show a list of all items of model but only the id and name fileds. Now I use this, but it show all not hidden fields:
public function index() {
return Form::->paginate(100);
}
This function is only for the list of forms names. But here is the second one for show a form datas for modify:
public function show(string $id) {
$item = Form::findOrFail($id);
return response()->json($item);
}
Of course this last one function needs to be show all fields (id, name, html and json too).
Is there any best practice to show only fields what I needed in the index() function using with paginate()?
If i am not wrong then hopefully you can do it something like this for getting specific columns along with pagination:
return Form::paginate(100,['id','name',.....]);
If I read your question correctly, what you want to do is create a collection of the Form object where only the id and the name fields are actually retrieved on the index overview.
You can do that pretty easily by creating a new collection instance in your controller:
public function index() {
// use the Eloquent select() function
$forms = Form::select('id', 'name')->paginate(100);
return $forms;
}
I would personally put that collection in a repository pattern to make it more easily cacheable. Here's a nice canonical reference to repository patterns in Laravel.
In your show function on your controller you needn't change a thing, considering the ID is still the same.
For future reference, remember that the paginate method paginates only the collection it is called on, and not everything related to a specific model or anything other than that collection. Thus, if you create a new collection in any way, and call the paginate method on that new collection, only whatever is inside that will be paginated. It's pretty powerful stuff! Here's the documentation reference.

Laravel eloquent attach auto generate random ID

I have an eloquent many to many relationship and I want to use attach() to easily create role_permissions data but the problem is I'm using an UUID for my ID and it throws an error Field 'id' doesn't have a default value. Any way of hijacking the attach() method? so I can set my UUID?
My migration
Schema::create('role_permissions', function (Blueprint $table) {
$table->increments('count')->unique();
$table->string('id')->unique();
$table->string('role_id');
$table->string('permission_id');
$table->timestamps();
});
My model
class Role extends Model
{
//
public $incrementing = false;
public function users()
{
return $this->belongsToMany('App\User', 'user_roles', 'role_id', 'user_id');
}
public function permissions()
{
return $this->belongsToMany('App\Permission', 'role_permissions', 'role_id', 'permission_id');
}
}
My attach code
$role->permissions()->attach($permission_ids);
I know the problem here is that my id is not an incrementing number it's an unique string. My question is how do I "Inject" that unique string to the attach() method? Thank you guys.
The error
Field 'id' doesn't have a default value
refers to the fact that your database does not know how to fill the id field when it's not specified.
Either you edit the schema adding a nullable:
Schema::create('role_permissions', function (Blueprint $table) {
$table->increments('count')->unique();
$table->string('id')->unique()->nullable(); // Bad idea
$table->string('role_id');
$table->string('permission_id');
$table->timestamps();
});
or injecting it via attach:
$role->permissions()->attach($permission_ids, ["id" => null]);
More info on Laravel official doc
Update
For the future developers who encounter this problem you can also set anything inside the attach array, for example:
$role->permissions()->attach($permission_ids, ["id" => Uuid::generate()]);
Update 2
There's also a more clean way to handle this to be honest. I will try to explain it.
You can handle the Pivot events inside the event service provider by simply hooking into the bootmethod:
Here's a snippet
/App/Providers/EventServiceProvider.php
public function boot()
{
Pivot::creating(function($pivot) {
if ($pivot->getTable() == 'role_permissions') {
$pivot->id = Uuid::generate();
}
});
}
Be aware I do not know if this is actually possible on your laravel version. Mine (5.4.*) works as intended
Okay managed to fixed it, what I did with the help of #Claudio Ludovico Panneta's tip.
foreach($permission_ids as $permission_id)
{
$role->permissions()->attach($permission_id, ["id" => Uuid::generate()]);
}

Resources