Laravel Relationship Collection iteration returns boolean - laravel

I have a recursive relationship (sections and sub sections)
defined as this in ReportSection model:
function sub_sections() {
return $this->hasMany('App\ReportSection', 'parent_id');
}
and I'm trying to iterate through it like so:
$section = Section::find($id);
\DB::beginTransaction();
try {
foreach(ReportForm::unlockedForm($section->form_id)->get() as $report) {
foreach($report->sections()->where('section_id', $section->id)->get() as $reportSections) {
\Log::info($reportSections);
foreach($reportSections as $rSection) {
\Log::info($rSection);
foreach($rSection->sub_sections as $subSection) {
The line \Log::info($reportSections); gives {"id":3,"report_form_id":1,"name_en":"DDD","name_fr":"DDD","created_at":"2016-11-29 07:47:24","updated_at":"2016-11-29 07:47:32","section_id":118,"parent_id":1,"order":99,"hidden":0} as expected
but the iterating through it somehow gives a boolean \Log::info($rSection); gives 1
The last line foreach($rSection->sub_sections as $subSection) { gives the error 'Trying to get property of non-object'
Why would iteration through a relationship collection give a boolean? What am I doing wrong?
Edit: changed sub_sections() to sub_sections but the error is still present

You should call the attribute name not the method:
foreach($rSection->sub_sections as $subSection)
{}

Ok after taking a break I was able to figure out that the problem was I was iterating through the same collection twice.
Instead of
foreach(ReportForm::unlockedForm($section->form_id)->get() as $report) {
foreach($report->sections()->where('section_id', $section->id)->get() as $reportSections) {
foreach($reportSections as $rSection) {
It should have been
foreach(ReportForm::unlockedForm($section->form_id)->get() as $report) {
foreach($report->sections()->where('section_id', $section->id)->get() as $rSection) {

Related

Laravel 5.7 many to many sync() not working

I have a intermediary table in which I want to save sbj_type_id and difficulty_level_id so I have setup this:
$difficulty_level = DifficultyLevel::find(5);
if ($difficulty_level->sbj_types()->sync($request->hard, false)) {
dd('ok');
}
else {
dd('not ok');
}
Here is my DifficultyLevel.php:
public function sbj_types() {
return $this->belongsToMany('App\SbjType');
}
and here is my SbjType.php:
public function difficulty_levels() {
return $this->hasMany('App\DifficultyLevel');
}
In the above code I have dd('ok') it's returning ok but the database table is empty.
Try to change
return $this->hasMany('App\DifficultyLevel');
to
return $this->belongsToMany('App\DifficultyLevel');
The sync() method takes an array with the id's of the records you want to sync as argument to which you can optionally add intermediate table values. While sync($request->hard, false) doesn't seem to throw an exception in your case, I don't see how this would work.
Try for example:
$difficulty_level->sbj_types()->sync([1,2,3]);
where 1,2,3 are the id's of the sbj_types.
You can read more about syncing here.

Laravel policies are resolving weird

I need a policy to create trees in a tournament.
So, In my treeController#store, I have:
if (Auth::user()->cannot('generateTree', new Tree(),$tournament)) {
throw new AuthorizationException();
}
And my corresponding policy is :
TreePolicy.php:
public function generateTree(Tree $tree, Tournament $tournament )
{
dd($tournament);
return ($tournament->user_id == Auth::user()->id);
}
And I get a :
Type error: Argument 1 passed to App\Policies\TreePolicy::generateTree() must be an instance of App\Tree, instance of App\User given, called in /laravel/vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php on line 382
What am I missing???
EDIT : In response to #andonovn,
I tried it with :
public function store(User $user, Tree $tree, Tournament $tournament)
{
dd($tournament);
}
And
if (Auth::user()->cannot('generateTree', Tree::class,$tournament)) {
throw new AuthorizationException();
}
--> it gives me a 403
Or
$this->authorize('store', $tournament,Tree::class);
--> it doesn't enter the dd();
The only way I found it to work is putting the Policy content in the controller which is not so nice:
if ($tournament->user_id != Auth::user()->id){
throw new AuthorizationException();
}
EDIT 2 : I solve it with that:
In controller :
if (Auth::user()->cannot('store', [Tree::class,$tournament])) {
throw new AuthorizationException();
}
In policy
public function store(User $user, Tournament $tournament)
{
return ($tournament->user_id == $user->id);
}
I believe the first argument of generateTree() must be the authenticated user. Try changing it to public function generateTree(User $user, Tree $tree, Tournament $tournament )
Edit:
Also change the cannot method to Auth::user()->cannot('generateTree', [Tree::class, $tournament]) (combine the 2nd and 3rd parameters in array, seems like Laravel is always expecting 2 arguments where the 2nd one can be array)

Querying counts from large datasets using eloquent

I have the following relationships:
A Job has many Roles.
public function roles()
{
return $this->hasMany(Role::class);
}
A Role has many Shifts and Assignments through shifts.
public function shifts()
{
return $this->hasMany(Shift::class);
}
public function assignments()
{
return $this->hasManyThrough(Assignment::class, Shift::class);
}
A Shift has many Assignments.
public function assignments()
{
return $this->hasMany(Assignment::class);
}
I need to get a count of all assignments with a certain status, let's say "Approved". These counts are causing my application to go extremely slowly. Here is how I have been doing it:
foreach ($job->roles as $role){
foreach ($role->shifts as $shift) {
$pendingCount = $shift->assignments()->whereStatus("Pending")->count();
$bookedCount = $shift->assignments()->whereIn('status', ["Booked"])->count();
}
}
I am certain that there must be a better, faster way. Some of these queries are taking upwards of 30+ seconds. There are hundreds of thousands of Assignments, which I know is affecting performance. How can I speed up these queries?
You're running into the N+1 issue here a few times. You want to lazy load the nested assignment through jobs then you can access the relation and your where() and whereIn() calls are executed on the returned collection instead of on the query builder which is why you have to use where('status', "Pending") instead of whereStatus("Pending") in my example because the collection won't automatically resolve this constraint:
$job = Job::with('roles.assignments')->find($jobId);
foreach ($job->roles as $role) {
$pendingCount = $role->assignments->where('status', "Pending")->count();
$bookedCount = $role->assignments->whereIn('status', ["Booked"])->count();
}
This should be a lot quicker for you.
UPDATE
You could even take that one step further and map the result and store the results in a property on the role:
$job->roles->map(function($role) {
$role->pending_count = $role->assignemnts->where('status', "Pending")->count();
$role->booked_count = $role->assignments->whereIn('status', ["Booked"])->count();
return $role;
});

Rethinkdb insert into nested array

I want to add to an array nested inside a table, appending a new item to the array.
But the returned run query is undefined. Please can anyone suggest a better way to run this?
rdb.table('SavedBaskets').get(basketId).run().then(function(result) {
let newPaymentHistory = [];
if ('paymentHistory' in result) {
newPaymentHistory = result.paymentHistory;
}
paymentHistory.push(charge);
return rdb.table('SavedBaskets').get(basketId).update({paymentHistory: newPaymentHistory}).run();
}).error(function(err) {
console.log(err);
});
You could use .append() such as
r.table("SavedBaskets").get(basketId).update(
{"paymentHistory": r.row("paymentHistory").append(charge)}
).run(conn)
https://www.rethinkdb.com/api/javascript/append/
Seen here
http://docs.rethinkdb.com/2.0/api/python/update/

.Max() method giving error when used in an if else

My application is in Asp.Net coded in C# and i'm using LINQ for database transactions. My requirement is to get the Max value of the records saved in a certain table, for this i'm using Max() method.
Below is my controller code :
[HttpPost]
public ActionResult Create(Entity_Name Entity_Object)
{
if (Entity_Object.Condition == true)
{
My required code
}
else
{
var get_Max_Number = db.Entity_Name.ToList();
long Max_Number = 0;
if (get_Max_Number.Count() > 0)
{
Max_Number = Convert.ToInt64(get_Max_Number.Max());
}
My required code
}
}
My issue is when i remove the If-else condition then the same Max() method query works perfect, but when i add the If-else statement then i gets the following error.
Error:
At least one object must implement IComparable.
What i tried :
I attempted to remove the If-Else
I placed the Max() method logic above the If-else
Placing the Max() method above If-Else
[HttpPost]
public ActionResult Create(Entity_Name Entity_Object)
{
var get_Max_Number = db.Entity_Name.ToList();
long Max_Number = 0;
if (get_Max_Number.Count() > 0)
{
Max_Number = Convert.ToInt64(get_Max_Number.Max());
}
if (Entity_Object.Condition == true)
{
My required code
}
else
{
My required code
}
}
Max() needs to know what you're getting the maximum of. If you're Entity_Name class contains a number of properties (strings, ints etc...) then you need to tell it what to get the Maximum on the basis of.
Another thing, you're connecting to a DB via Linq from the looks of things, but executing your Count() & Max() functions in memory after you've retrieved the entire contents of the database table. This will be very inefficient as the table grows in size. LinqToSql & LinqToEF support pushing those functions down to the database level. I'd recommend changing your code to the following.
[HttpPost]
public ActionResult Create(Entity_Name Entity_Object)
{
if (Entity_Object.Condition == true)
{
//My required code
}
else
{
long Max_Number = 0;
if(db.Entity_Name.Count() > 0)
{
Max_Number = Convert.ToInt64(
db.Entity_Name.Max(x => x.PropertyToGetMaxOf)
);
}
//My required code
}
}

Resources