Laravel Nova Metrics Partition belongsToMany relationship - laravel

I have the following data model:
(Publication) <-[belongsToMany]-> (Subscriber)
I want to create a Nova Partition Metric to display the number of Subscribers for each Publication.
The calculate method of my Partition class looks like this:
public function calculate(Request $request)
{
return $this->count($request, Subscriber::with('publications'), 'publication.id');
}
But I am getting an "unknown column" error. Anyone know how to make this work?

You could do something like this:
public function calculate(Request $request)
{
$subscribers = Subscriber::withCount('publications')->get();
return $this->result(
$subscribers->flatMap(function ($subscriber) {
return [
$subscriber->name => $subscriber->publications_count
];
})->toArray()
);
}

The count helper only allows to group by a column on model's table. It also don't allow to join tables.
If you want a more complex query, with a join and a group by column in another table, you can build your own array of results and return it with the results helper.
You can see the results helper docs here: https://nova.laravel.com/docs/1.0/metrics/defining-metrics.html#customizing-partition-colors
You should create your array (you can use eloquent or query builder here) inside the calculate function, then return that array with the results helper.
Hope this helps!

You can make the groupBy on publication_foreign_key in Subscriber table and edit the publication_foreign_key to publication_name using ->label() method
Like this
public function calculate(Request $request)
{
return $this->count($request, Subscriber::class, 'publication_id')
->label(function($publicationId)
{
switch($publicationId)
{
case publication_foreign_key_1 : return publication_name_1;
break;
case publication_foreign_key_2 : return publication_name_2;
break;
default: return 'Others';
}
});
}

Related

Laravel Model request with several joins

While I am able to make simple requests with Model, I can't say the same for more complicated ones.
I know I don't necessarily have to use Model and can use DB facade but still, I want to know how it's supposed to be done.
Here's a request I made using DB :
DB::table('relationships')
->Join('users','users.id','=','relationships.user_id')
->Join('roles','roles.id','=','relationships.role_id')
->Join('bundles','bundles.id','=','relationships.related_id')
->Join('pools','bundles.id','=','pools.bundle_id')
->whereIn('pools.name',$pools)
->whereIn('roles.name',$roles)
->select('users.first_name','users.last_name','users.mail_address','roles.name AS role_name','bundles.name AS bundle_name', 'pools.name AS pool_name')
->get();
On a first attempt, I tried this:
User::whereHas('relationships', function($req) use($roles) {
$req->whereHas('bundle', function($req){
$req->whereIn('name', $pools);
});
$req->whereHas('role', function ($req){
$req->whereIn('name', $roles);
});
})
->with('relationships', 'relationships.role:id,name', 'relationships.bundle:id,name')
->get();
}
Problem is, using "with" just select everything unconditionally, ignoring previous tests you made earlier (whereHas, whereIn).
So I'd have to again filter on each table in the with statement.
Then I ended up doing this:
$pools = request()->input('pools.*.name');
return $prepReq = User::whereHas('relationships', function($req) use($pools, $roles) {
$req->whereHas('bundle', function($req) use ($pools){
$req->whereHas('pools', function($req) use ($pools){
$req->whereIn('name', $pools);
});
});
$req->whereHas('role', function ($req){
$req->whereIn('name', $roles);
});
})
->with(['relationships' => function ($query) use($pools, $roles){
$query->whereHas('role', function ($query){
$query->whereIn('name', $roles)
->select('id','name');
})->select('id','name');
}])
->get(['id', 'first_name', 'last_name', 'mail_address']);
Then I got lost into this and gave up.
Another thing that made me sweat is that when you go nested using "with", you can select columns only on the last table.
For example: "relations.bundle.pools" => I can select columns on pools but not on relationships or bundles, does that mean i have to imbricate with statements for each table ?
As you can see, I am a bit clueless on how things are supposed to be done
I would like any advice or help regarding this matter
Thanks in advance for your time
When using Laravel, you should be setting up the eloquent relationships for each model
Based on your select statement from above, and assuming you want to get the user, bundle, role, and pool, then I would do the following, may be off depending on how your actual DB and models are set up
// Relationship.php
public function user()
{
return $this->belongsTo(User::class);
}
public function role()
{
return $this->belongsTo(Role::class);
}
public function bundle()
{
return $this->belongsTo(Bundle::class, 'id', 'related_id');
}
// Bundle.php
public function relationship()
{
return $this->hasOne(Relationship::class, 'related_id');
}
// Role.php
public function relationship()
{
return $this->hasOne(Relationship::class);
}
// Pool.php
public function bundle()
{
return $this->belongsTo(Bundle::class);
}
Then you could do something like
Pool::with('bundle.relationship.user')->whereIn('name', $pools);
Role::with('relationship.user')->whereIn('name', $roles);

How to update multiple Rows In Laravel Controller

basically I want to update this table in one go or bulk update.
I wanted to update the "image_id" field on this table.
This is my code in the Controller
public function storebulk(Request $request,$id)
{
$ids= ['8','9','10']; //sent from the front-end
$barcode = Barcode::where('id', $ids)->update(['image_id'=>$id]);
return 'Done';
}
but for some reason it doesn't work. if anyone can point out what I missed here it will be great.
thanks
You should use whereIn:
public function storebulk(Request $request, $id)
{
$ids= ['8','9','10']; //sent from the front-end
$barcode = Barcode::whereIn('id', $ids)->update(['image_id'=>$id]);
return 'Done';
}
In Laravel, If you want to update multiple rows or get multiple rows of same type always use whereIn.
The Docs Define whereIn as -
The whereIn method verifies that a given column's value is contained within the given array
public function storebulk(Request $request,$id)
{
$ids= ['8','9','10']; //sent from the front-end
$barcode = Barcode::whereIn('id', $ids)->update(['image_id'=>$id]);
return 'Done';
}
Link to docs

Customizing the value of Partition Metrics in Laravel nova

I have a table users that has relationship to operations.
\App\user:
public function operation()
{
return $this->belongsTo('App\Operation');
}
So I can do:
$operation = user->operation
Now I want to add a Partition Metric in the Users resource page that tells me the number of users grouped by operation
Right now, it display an id, and it not filtering my users ( need to filter query with company_id )
public function calculate(NovaRequest $request)
{
return $this->count($request, User::class, 'operation_id', 'name');
}
I tried to add name column name, but first it is not working, and second, I would need the name of the operation, not the user, so it should be something like operation.name but it is not working neither.
How can I print the operation name instead of operation_id
PD: Basically, whole process it being more painful because I can't see the output of dump() or dd() in a metric, so I can't debug.
You should use ->label() method as mentioned in Laravel/nova docs:
https://nova.laravel.com/docs/3.0/metrics/defining-metrics.html#customizing-partition-labels.
Then, the correct code should be:
public function calculate(NovaRequest $request)
{
return $this->count($request, User::class, 'operation_id')
->label(function($operationId){
switch($operationId){
case 'opration_1_id': return 'operation_1_name';
break;
case 'opration_2_id': return 'operation_2_name';
break;
default: return 'Other';
}
});
}

Laravel firstOrCreate without Eloquent

Eloquent has a firstOrCreate method which gets a model based on a condition, or creates it if it doesn't exist.
Is there any equivalent method in Laravel's query builder (i.e. NOT in Eloquent)? For example:
$row = DB::table('users')->where('user_id', 5)->firstOrCreate('name' => 'Peter', 'last_name' => 'Pan');
That would try to get a row from users with 'user_id'==5. If it doesn't exist, it would insert a row with that id number, plus the other mentioned fields.
EDIT: I'm not trying to apply my question with users. I used users as an example to make as clear as possible what I'm looking for.
updateOrInsert function with empty values give me the result like firstOrCreate
Nope, Laravel firstOrCreate is function, that says next:
public function firstOrCreate(array $attributes, array $values = [])
{
if (! is_null($instance = $this->where($attributes)->first())) {
return $instance;
}
return tap($this->newModelInstance($attributes + $values), function ($instance) {
$instance->save();
});
}
But you can add it with query micro:
DB::query()->macro('firstOrCreate', function (array $attributes, array $values = [])
{
if ($record = $this->first()) {
// return model instance
}
// create model instance
});
So than you will be able to call it same way you do with Eloquent.
$record= DB::table('records')->where('alias', $alias)->firstOrFail();
Yeah of course! Just use normal SQL and ->selectRaw( your conditions ) and look for if there is a entry where your specifications are.
https://laravel.com/docs/5.7/queries#raw-expressions

Laravel oneToMany accessor usage in eloquent and datatables

On my User model I have the following:
public function isOnline()
{
return $this->hasMany('App\Accounting', 'userid')->select('rtype')->latest('ts');
}
The accounting table has activity records and I'd like this to return the latest value for field 'rtype' for a userid when used.
In my controller I am doing the following:
$builder = App\User::query()
->select(...fields I want...)
->with('isOnline')
->ofType($realm);
return $datatables->eloquent($builder)
->addColumn('info', function ($user) {
return $user->isOnline;
}
})
However I don't get the value of 'rtype' for the users in the table and no errors.
It looks like you're not defining your relationship correctly. Your isOnline method creates a HasMany relation but runs the select method and then the latest method on it, which will end up returning a Builder object.
The correct approach is to only return the HasMany object from your method and it will be treated as a relation.
public function accounts()
{
return $this->hasMany('App\Accounting', 'userid');
}
Then if you want an isOnline helper method in your App\User class you can add one like this:
public function isOnline()
{
// This gives you a collection of \App\Accounting objects
$usersAccounts = $this->accounts;
// Do something with the user's accounts, e.g. grab the last "account"
$lastAccount = $usersAccounts->last();
if ($lastAccount) {
// If we found an account, return the rtype column
return $lastAccount->rtype;
}
// Return something else
return false;
}
Then in your controller you can eager load the relationship:
$users = User::with('accounts')->get(['field_one', 'field_two]);
Then you can do whatever you want with each App\User object, such as calling the isOnline method.
Edit
After some further digging, it seems to be the select on your relationship that is causing the problem. I did a similar thing in one of my own projects and found that no results were returned for my relation. Adding latest seemed to work alright though.
So you should remove the select part at very least in your relation definition. When you only want to retrieve certain fields when eager loading your relation you should be able to specify them when using with like this:
// Should bring back Accounting instances ONLY with rtype field present
User::with('accounts:rtype');
This is the case for Laravel 5.5 at least, I am not sure about previous versions. See here for more information, under the heading labelled Eager Loading Specific Columns
Thanks Jonathon
USER MODEL
public function accounting()
{
return $this->hasMany('App\Accounting', 'userid', 'userid');
}
public function isOnline()
{
$rtype = $this->accounting()
->latest('ts')
->limit(1)
->pluck('rtype')
->first();
if ($rtype == 'Alive') {
return true;
}
return false;
}
CONTROLLER
$builder = App\User::with('accounting:rtype')->ofType($filterRealm);
return $datatables->eloquent($builder)
->addColumn('info', function (App\User $user) {
/*
THIS HAS BEEN SUCCINCTLY TRIMMED TO BE AS RELEVANT AS POSSIBLE.
ARRAY IS USED AS OTHER VALUES ARE ADDED, JUST NOT SHOWN HERE
*/
$info[];
if ($user->isOnline()) {
$info[] = 'Online';
} else {
$info[] = 'Offline';
}
return implode(' ', $info);
})->make();

Resources