Add some records to all eloquent query results - laravel

I have a table named food_portion like the following:
id|food_id|name|gram_weight
1|102030|slice|183
2|102030|pie|183
3|102031|waffle|35
....
The table is complete, But some global portions are missing like gram/oz..
I wanted to write a query to add records for this portions but I'm thinking that its not a good choice because this portions have same value for all the foods.
*|*|gr|1 (6000 records like this)
*|*|oz|28 (and another 6000 like this)
So I'm looking for a way to modify my model (food_portion) so every time I execute some query using model get the the above records without having them physically in the database table, So my queries wouldn't be slow for no reason.
How can I do this. I tried to do this using global scope but I failed:
protected static function booted()
{
static::addGlobalScope('global_portions', function (Builder $builder) {
$builder->orWhere( function($query)
{
//$query->where("food_id","*")->where("name","gr") ???
// what should I write here?
});
});
}
Bottom line is I want to prevent record repetition for every food.
I want to add two specific records to every query result.
Thanks in advance

I think you are very close, check this:
use Illuminate\Support\Facades\DB;
protected static function booted()
{
static::addGlobalScope('global_portions', function (\Illuminate\Database\Eloquent\Builder $builder) {
$builder->union(DB::query()->select([
DB::raw("\"*\" AS id"),
DB::raw("\"*\" AS food_id"),
DB::raw("\"gr\" AS name"),
DB::raw("\"1\" AS gram_weight"),
]));
});
}
This is to add one record. To add more, simply chain more union functions, or edit the query inside.
Note: For Laravel 6.x use "boot" instead of "booted", and add a line parent::boot(); before addGlobalScope

Related

Eloquent - apply value from relationship to where in scope

So I have this need to check if a customer needs to be called. Customers has to be called at intervals depending on a value days_between_calls in a BelongsTo model called SubscriberType. I got it to work but I don't like it, maybe there is a cleaner way.
So I have a model Subscription with relations :
public function subscriberType()
{
return $this->belongsTo(SubscriberType::class);
}
public function calls()
{
return $this->hasMany(Call::class);
}
and a (very simplified) scope :
public function scopeNeedsCall(Builder $query) {
$query->join('subscriber_types', 'subscriber_types.id', '=', 'subscriptions.subscriber_type_id')
->whereDoesntHave('calls', function(Builder $query) {
$query->whereRaw('calls.created_at > DATE_SUB(NOW(), INTERVAL days_between_calls DAY)');
});
}
Is there any cleaner way to use this days_between_calls field's value without manually joining its table and without writing raw sql?
Thanks ahead.
So it looks like there is not much that can be improved, and I do need a rawsql part here. I improved it a little anyway using https://laravel.com/docs/9.x/eloquent-relationships#has-one-of-many but that's about it.

Extending Eloquent Models with dynamic global scopes

I have a MySQL table that receives many different Jotform reports in JSON payloads. This has helped tremendously in capturing queryable data quickly without adding to the front-end developer's workload.
I created an eloquent model for the table. I now would like to be able to create models that extend it for each Jotform we create. I feel like it will increase the readability of my code drastically.
My eloquent model is called RawDataReport. It has created_at, updated_at, data, and report name columns in the table. I want to create the model ShiftInspectionReport extending the RawDataReport.
I have two JotForm reports one is called Shift Inspection Report and one is called Need Inspection Report. Both are part of the ShiftInspectionReport model.
So I need to query the RawDataReports table for any reports matching those names. I frequently need to query the RawDataReports report_name column with either one or more report names.
To help with this I created a local scope to query the report name which accepts either a string report name or an array of string report names. Here is the local scope on the RawDataReports model.
protected function scopeReportName($query, $report_name): \Illuminate\Database\Eloquent\Builder
{
if (is_array($report_name)) {
return $query->orWhere(function ($query) USE ($report_name) {
ForEach($report_name as $report) {
if (is_string($report) === false) {
throw new \Exception('$report_name must be an array of strings or a string');
}
$query->where('report_name', $report);
}
});
} else {
if (is_string($report_name) === false) {
throw new \Exception('$report_name must be an array of strings or a string');
}
return $query->where('report_name', $report_name);
}
}
EDIT - after comments I simplified the reportName scope
protected function scopeReportName($query,array $report_name): \Illuminate\Database\Eloquent\Builder
{
return $query->whereIn('report_name',$report_name);
}
Now in my ShiftInspectionReport model, I'd like to add a global scope that can use that local scope and pass in the $report_name. But according to this article, Laravel 5 Global Scope with Dynamic Parameter, it doesn't look like Laravel global scopes allow you to use dynamic variables.
I could just create a local scope in ShiftInspectionReport but the readability would look like
$reports = ShiftInspectionReport::shiftInspectionReport()->startDate('2021-05-15')->get()
when I'd really like to be able to just call
ShiftInspectionReport::startDate('2021-05-15')->get()
Any suggestions or comments would be appreciated.
Thank you
Thanks to IGP I figured out that I can just call the local scope right from my boot function.
My extended class looks like this now and it works.
class ShiftInspection extends RawDataReport
{
use HasFactory;
protected static function booted()
{
static::addGlobalScope('shift_inspection_report', function(\Illuminate\Database\Eloquent\Builder $builder) {
$builder->reportName(['Shift Safety Inspection','Need Safety Inspection']);
});
}
}

How to select multiple row values coma separated in laravel

I am trying to get all the ids with coma separated while doing eloquent relationship.
So here is my current queries
Divrank::where('division_id', 591)->with('meta')->orderBy('position', 'asc')->get()
Divrank table has a one to many relation with Divrankmeta model. So with meta I am trying to return
public function meta(){
return $this->hasOne(Divrankmeta::class)->selectRaw('id, match_id,divrank_id, sum(won) as won, sum(loss) as loss, sum(draw) as draw, sum(points) as points, sum(matchePlayed) as matchePlayed, sum(totalSets) as totalSets, sum(totalGames) totalGames')
->groupBy('divrank_id');
}
So far this query works fine..
I get the result like this screenshot
Ok so in my Divrankmeta model, I have a column called winAgainst and it can have some ids and some left null. So with the meta relation I want to retrieve winAgainst ids with coma separated string inside meta object.
For better understanding, here is how Divrankmeta table looks like
How can I do this?
Thank you.
The relation you created is one-to-one not one-to-many. That's why you are getting a meta object of the first matched row instead of an array that contains all related meta records.
I never put the modification codes into the eloquent functions. Those codes seem belongs to somewhere else. From my perspective, using "resources" and modifying the data there is a better idea.
If you chose the do so:
// Divrank.php
public function metas()
{
return $this->hasMany('App\Models\Divrankmeta');
}
// Divrankmeta.php
public function divrank()
{
return $this->belongsTo('App\Models\Divrank');
}
// DivrankController
public function index()
{
return DivrankResource::collection(Divrank::with("metas")->all());
}
Create a resource file.
php artisan make:resource DivrankResource
Now, you can modify your Divrank collection on the resource file before your controller returns it.
public function toArray($request)
{
$metaIds = [];
forEach($this->metas as $meta) {
array_push($metaIds, $meta['id']);
}
$this['metaIds'] = $metaIds;
return parent::toArray($request);
}
I'm not able to test this code. But it will probably work. If you don't want to use resources, you can create the same functionality in your controller as well. Bu we like to make controllers as short as possible.
Ok I think I solved it, These are the changes I did. Thanks
return $this->hasOne(Divrankmeta::class)
// ->selectRaw('id, match_id,divrank_id, sum(won) as won, sum(loss) as loss, sum(draw) as draw, sum(points) as points, sum(matchePlayed) as matchePlayed,
// sum(totalSets) as totalSets, sum(totalGames) totalGames')
->select(\DB::raw("id, match_id,divrank_id, sum(won) as won, sum(loss) as loss, sum(draw) as draw, sum(points) as points, sum(matchePlayed) as matchePlayed,
sum(totalSets) as totalSets, sum(totalGames) totalGames, GROUP_CONCAT(winAgainst) as winAgainst"))->groupBy('divrank_id');

Fetching groups and the size of every group

So, i have a mongo database filled(21k enteries) with columns like action(there are 5 different actions) id, time, etc.
I need to get the name of every action, and how many times does this action occur. For example: USERPROPERTY_CHANGED - 755
I have tried pretty much everything in here Laravel Eloquent groupBy() AND also return count of each group
Then i tried to make anothe collection, where i input the fields one, by one, and then fetch them, but my migration looks like this:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class actionPopularity extends Migration
{
public function up()
{
Schema::create('actionPopularity', function (Blueprint
$collection) {
$collection->Increments('id');
$collection->string('action');
$collection->integer('count');
});
}
public function down()
{
Schema::dropIfExists('actionPopularity');
}
}
But the collection that this generated had only one field - id.
heres something that kind of works (it shows that it can work with the database)
Controller:
public function database_test()
{
$actions = Action::groupBy('action')->get();
return view('database_test',compact('actions'));
}
view:
{{$actions}}
output:
[{"_id":
{"action":"OBJECT_INSERT"},"action":"OBJECT_INSERT"},
{"_id":
{"action":"OBJECT_MODIFY"},"action":"OBJECT_MODIFY"},
{"_id":
{"action":"null"},"action":"null"},{"_id":
{"action":"USERPROPERTY_CHANGED"},"action":"USERPROPERTY_CHANGED"},
{"_id":{"action":"OBJECT_DELETE"},"action":"OBJECT_DELETE"}]
ultimately i want to get two arrays, one with action names, and another one with the amount of times that this action has been called, to put it in a chart.

How to fetch two related objects in Laravel (Eloquent) with one SQL query

I am trying to get two related objects in Laravel using eager loading as per documentation.
https://laravel.com/docs/5.4/eloquent-relationships#eager-loading
My models are:
class Lead extends Model {
public function session() {
return $this->hasOne('App\LeadSession');
}
}
class LeadSession extends Model {
public function lead() {
return $this->belongsTo('App\Lead');
}
}
I want to get both objects with one SQL query. Basically I want to execute:
select * from lead_sessions as s
inner join lead as l
on l.id = s.lead_id
where s.token = '$token';
and then be able to access both the LeadSession and Lead objects. Here is the php code I am trying:
$lead = Lead::with(['session' => function ($q) use ($token) {
$q->where('token','=',$token);
}])->firstOrFail();
print($lead->session->id);
I have also tried:
$lead = Lead::whereHas('session', function($q) use ($token) {
$q->where('token','=',$token);
})->firstOrFail();
print($lead->session->id);
and
$session = LeadSession::with('lead')->where('token',$token)->firstOrFail();
print($session->lead->id);
In all three cases I get two queries executed, one for the leads table, and another for the lead_sessions table.
Is such a thing possible in Eloquent? In my view it should be a standard ORM operation, but for some reason I am struggling a whole day with it.
I don't want to use the Query Builder because I want to use the Eloquent objects and their functions afterwards.
I am coming from Python and Django and I want to replicate the behavior of select_related function in Django.
Try this and see if it makes more than one query
$session = LeadSession::join('leads', 'leads.id', '=', 'lead_sessions.lead_id')
->where('token',$token)
->firstOrFail();
I hope it only runs a single query. I didnt test this. Not sure if you have to add a select() to pick the columns. But yeah, try this first.
Updates
Just adding how to use both session and lead data. Try a select and specify the data you need. The reason being that if both tables have similar columns like 'id', one of them will be overwritten. So you have to alias your select like
$session = LeadSession::join('leads', 'leads.id', '=', 'lead_sessions.lead_id')
->where('token',$token)
->select(
'lead_sessions.*',
'leads.id as lead_id',
'leads.name',
'leads.more_stuff'
)
->firstOrFail();
Now all this data belongs to $session variable. For testing you were doing
print($lead->session->id);
//becomes
print($session->lead_id); //we aliased this in the query

Resources