Laravel Eloquent group results by relation - laravel

I am struggling to get Laravel Eloquent to retrieve and group results in the way that i'd like.
Basically I am creating a 'My Agenda' page which shows all task in order of their due date, but grouped together if 2 or more Tasks (in order) belong to the same Stage, and likewise if 2 or more stages belong to the same project.
My data is Projects -> (has many) Stages -> (has many) Tasks
I would like to output my data as follows:
Project B
Stage 2
Task 1 (due 1st Sep)
Task 3 (due 2nd Sep)
Stage 1
Task 2 (due 3rd Sep)
Project A
Stage 1
Task 2 (due 4th Sep)
Project B <---- repeated as Project A has a stage->task due before these tasks due
Stage 3
Task 2 (due 5th Sep)
Project A <---- repeated as Project B has a stage->task due before
Stage 1 <---- repeated
Task 1 (due 6th Sep)
Any ideas how I can achieve this? I am open to doing this on the front end with JS/Vue/Lodash.
Thanks in advance!
M

I think you can do it this way:
First, let's combine all the tables with JOIN. If you want to see all projects and stages that don't have any relational data, you can use LEFT JOIN, or maybe RIGHT JOIN, I don't know which one will work.
$tasks = Task::orderBy("due")
->join("stages", "stages.id", "=", task.stage_id)
->join("projects", "projects.id", "=", stages.project_id)
->select("pick the columns you want to have")
->get();
I think you should aim for this type of array as your output, so you won't have any issues because of the repeated key names.
/*
$output = [
[
'project'=> A,
'stages'=> [
stage_name => [task 1, task 2],
stage_name => [task 4, task 8],
],
],
[
'project'=> B,
'stages'=> [
stage_name => [task 5],
],
],
[...]
];
*/
To create that type of array, the function down below should work.
$output = [];
foreach($tasks => $task) {
$project = $task['project_name'];
$lastEntry = $output[count($output) - 1];
if ( count($output) > 0 && $lastEntry['project'] == $project) {
// this means $task should be inserted in the last array.
// You should check for stages.
if (array_key_exists($task['stage_name'], $lastEntry['stages'])) {
$lastEntry['stages'][$task['stage_name']][] = $task;
} else {
$lastEntry['stages'][$task['stage_name']] = [$task];
}
// I think $lastEntry['stages'][$task['stage_name']][] = $task; will work without checking stage names, but I can't be sure, you need to try it.
} else {
// This means you should create a new item in $output.
$output[] = [
'project' => name,
'stages' => [
'stage_name' => [$task];
]
]
}
}
I created those codes here directly. There can be typos and everything, but the logic should work.

Related

Eloquent not updating first item in collection

I don't understand why this function won't update the first app and change its lock state to 0 and rest to 1. It should update all apps with pending status.
public function updateLockState( $stu_id )
{
$apps = Application::where('stu_id', $stu_id)->get();
$acceptedApps = $apps->whereIn('status', [ 'ACCEPTED_W_SCHOLARSHIP', 'ACCEPTED_WO_SCHOLARSHIP', 'ACCEPTED_BYSTUDENT', 'ACCEPTED_CONDITIONALLY', 'UNDER_REVIEW', 'REGISTERED' ]);
if ( $acceptedApps->count() == 0 )
{
// This has two apps showing in descending order by rank.
$pending_apps = $apps->where('status', 'PENDING')->sortByDesc('rank');
foreach ($pending_apps as $key => $value)
{
if ( $key == 0 )
{
$value->update(['locked' => 0]);
}
else
{
$value->update(['locked' => 1]);
}
}
}
dd();
}
I have even tried to use DB:table() function for updating apps with id in where clause but it gives same result. This function is executed on the first line of the index function i.e its the first function to run on the page that interacts with apps. It ends with a dd() or exit() which stops anything else to run.
EDIT:
When I manually update rank values in db, I run this script and it should update locks but it is not. It should make lock = 0 for first app and lock = 1 for all other app and since apps are ordered by ranks desc it, app with 18 rank should be on top hence its lock = 0 after update:
sortByDesc() sorts in descending order BUT it keeps the keys in the same order in the new collection that it returns.
So even if its desc order the first item in the collection $key may not always be 0.
Use:
$pending_apps = $apps->where('status', 'PENDING')->sortByDesc('rank')->values()->all();
Which will give you keys starting with 0.

Cakephp 3 How to make session array

I am trying to write session in controller. My structure is
$_SESSION['a'][0] = 1;
$_SESSION['a'][1] = 2;
$_SESSION['a'][2] = 3;
And I am trying this
Configure::write('Session', ['a' =>'1'])
But it is not working. How do this in cakephp 3 way
To write variable in Session in CakePHP 3 you need to write following code :
$this->request->session()->write('Your Key',Your_array);
To know more information you can visit here :
http://book.cakephp.org/3.0/en/development/sessions.html
To make things perfectly clear:
// code writing array to session
$a = [ "abc" => "word", "123" => 42, "?" => $b ];
$a["more"] = "if you need to add";
$a[] = "whatever";
$this->request->session()->write( 'my_array', $a );
// code reading array from session
$recall = $this->request->session()->read( 'my_array' );
debug( sprintf( "What's the word? [%s]", $recall["abc"] ) );
You can simply use
$session->write([
'key1' => 'blue',
'key2' => 'green',
]);
I am refering to
http://book.cakephp.org/3.0/en/development/sessions.html#reading-writing-session-data
The answer is that this cannot be done in CakePHP 3.x
In vanilla PHP, it's possible to do this:
<?php
session_start();
$_SESSION['a'][0] = 1;
$_SESSION['a'][1] = 2;
$_SESSION['a'][2] = 3;
var_dump($_SESSION);
?>
Which will output:
array(1) {
["a"]=> array(3) {
[0]=> int(1)
[1]=> int(2)
[2]=> int(3)
}
}
This is correct, and what should happen.
In CakePHP, you cannot specify arrays in the session key. For example:
$this->request->session()->write('a[]', 1);
$this->request->session()->write('a[]', 2);
$this->request->session()->write('a[]', 3);
Will not work.
If you remove the [] the value will get overwritten. For example:
$this->request->session()->write('a', 1);
$this->request->session()->write('a', 2);
$this->request->session()->write('a', 3);
The value of $this->request->session()->read('a') would be 3. The values 1 and 2 have been overwritten. Again, this is to be expected because you're overwriting the key a each time. The equivalent vanilla PHP for this is:
$_SESSION['a'] = 1;
$_SESSION['a'] = 2;
$_SESSION['a'] = 3;
Due to the lack of an indexed array, $_SESSION['a'] gets overwritten each time. This is normal behaviour. It needs to have the indexes (e.g. ['a'][0], ['a'][1], ...) to work!
The other answers where they have given things like key1 and key2 are not appropriate. Because there are many situations where you want everything contained within an indexed array. Generating separate key names is wrong for this type of scenario.
My edit of the accepted answer was rejected, so I present the - seemingly necessary - explicit code example, for the benefit of #Andy and others.
// code to write to session
$a = [ 0 => "zero", 1 => "one", 2 => "two" ];
$a[] = "three";
$this->request->session()->write( 'my_array', $a );
// code to read from session
$z = $this->request->session()->read( 'my_array' );
debug( $a[3] ); // outputs "three"

Shortcut LINQ toentity query for the following code in MVC 3

I have entities which are indeirectly connected. Here, Task is connected to Sprint indirectly. One BacklogItem can have several Task and each BacklogItem belongs to one Sprint. So, In my application I needed all the Tasks belonging to some sprint. Since they do not connect directly, I had to write a many code lines which is below:
public viewResult tasksForSprint(int sprintId){
List<Task> tasksforSprint = new List<Task>();
var backlogItemlsit = db.BacklogItems.Where(b => b.sprintId == sprintId).OrderBy(i => i.backlogId).ToList();
var sprintTaskItems = db.Tasks.OrderBy(i => i.taskId).ToList();
foreach (var item in sprintTaskItems)
{
if (backlogItemlsit.Any(b => b.backlogId == item.backlogId))
taskforSprint.Add(item);
}
return view(tasksforSprint);
}
Now, what I want is get that List tasksforSprint with one LINQ query rather than 6 lines of code.
Thanks in advance.
Looks like it's a join. Untested:
db.BacklogItems
.Where(b => b.sprintId == sprintId)
.Join(db.Tasks,
// outer join key
sprint => sprint.backlogId,
// inner join key
task => task.backlogId,
// result selector -- take the tasks
(outer, inner) => inner)
.OrderBy(task => task.taskId)
.ToList();

linq sub-query table cross reference

I want to conditionally exclude items from a query of tableA if an ID value for that query is NOT included at least once as a reference value in tableB... Something like this...
Initial query:
var jobs = from j in Jobs select j; // there's more, just keeping it simple...
I have tried these sub-queries...
Optional filtering based on a conditional:
jobs = jobs.Where(j => Bidders.Select(b => b.JobKey == j.JobKey) != null);
OR this:
jobs = jobs.Where(j => Bidders.Select(b => b.JobKey == j.JobKey).Count() > 0);
This does not seem to filter out jobs with no entries in the bidders table...
How should I do this???
If there is no navigation property already you were somewhat close with the first approach:
jobs = jobs.Where(j => Bidders.Any(b => b.JobKey == j.JobKey));

increment value in a hash

I have a bunch of posts which have category tags in them.
I am trying to find out how many times each category has been used.
I'm using rails with mongodb, BUT I don't think I need to be getting the occurrence of categories from the db, so the mongo part shouldn't matter.
This is what I have so far
#recent_posts = current_user.recent_posts #returns the 10 most recent posts
#categories_hash = {'tech' => 0, 'world' => 0, 'entertainment' => 0, 'sports' => 0}
#recent_posts do |cat|
cat.categories.each do |addCat|
#categories_hash.increment(addCat) #obviously this is where I'm having problems
end
end
end
the structure of the post is
{"_id" : ObjectId("idnumber"), "created_at" : "Tue Aug 03...", "categories" :["world", "sports"], "message" : "the text of the post", "poster_id" : ObjectId("idOfUserPoster"), "voters" : []}
I'm open to suggestions on how else to get the count of categories, but I will want to get the count of voters eventually, so it seems to me the best way is to increment the categories_hash, and then add the voters.length, but one thing at a time, i'm just trying to figure out how to increment values in the hash.
If you aren't familiar with map/reduce and you don't care about scaling up, this is not as elegant as map/reduce, but should be sufficient for small sites:
#categories_hash = Hash.new(0)
current_user.recent_posts.each do |post|
post.categories.each do |category|
#categories_hash[category] += 1
end
end
If you're using mongodb, an elegant way to aggregate tag usage would be, to use a map/reduce operation. Mongodb supports map/reduce operations using JavaScript code. Map/reduce runs on the db server(s), i.e. your application does not have to retrieve and analyze every document (which wouldn't scale well for large collections).
As an example, here are the map and reduce functions I use in my blog on the articles collection to aggregate the usage of tags (which is used to build the tag cloud in the sidebar). Documents in the articles collection have a key named 'tags' which holds an array of strings (the tags)
The map function simply emits 1 on every used tag to count it:
function () {
if (this.tags) {
this.tags.forEach(function (tag) {
emit(tag, 1);
});
}
}
The reduce function sums up the counts:
function (key, values) {
var total = 0;
values.forEach(function (v) {
total += v;
});
return total;
}
As a result, the database returns a hash that has a key for every tag and its usage count as a value. E.g.:
{ 'rails' => 5, 'ruby' => 12, 'linux' => 3 }

Resources