Laravel 5.5 - Merge 2 collections into one based on id? - laravel

So I have 2 models Books and Classes:
$books = Books::limit(3)->get(['id','classable_id','easy_book']);
// Books returned:
{ id: 200,
classable_id: 2,
easy_book: false
},
{ id: 201,
classable_id: 3,
easy_book: true
},
{ id: 202,
classable_id: 4,
easy_book: false
}
$classIds = $books->pluck('classable_id');
$classes = Classes::whereIn('id', $classIds);
// Classes returned:
{ id: 2,
subject: Math,
students: 30
},
{ id: 3,
subject: History,
students: 30
},
{ id: 4,
subject: Physics,
students: 30
}
Then trying to get the following output (without combining the queries, but keeping them separate like above, and just using php logic to output):
Classes returned:
{ id: 2,
subject: Math,
students: 30.
easy_book: false }, // trying to merge this!
{ id: 3,
subject: History,
students: 30.
easy_book: true}, // trying to merge this!
{ id: 4,
subject: Physics,
students: 30.
easy_book: false } // trying to merge this!
Basically, I am trying to merge the easy_book field from books returned to the respective class returned based on class.id == books.classable_id. Any idea how to merge it?

Add a relationship to your Books model like so:
public function class() {
return $this->belongsTo(Classes::class, 'id', 'classable_id);
}
Then you can do:
Book::with('class')->select('id', 'classable_id', 'easy_book')->limit(3)->get();
Each collection item will then have a collection of classes where applicable.
If after that you want to manipulate them, you can use the map function as documented here: https://laravel.com/docs/5.7/collections#method-map

Related

How to hide columns when using "with" in Laravel?

How would I hide unwanted data for a cleaner response in laravel when using with statements.
For example, let's pretend I have a single post. That post has comments, and then comments have multiple tags related to each comment.
So I have below to illustrate the idea.
I have a model called Post
I then have a relationship in Post model that has comments relationship, ie
public function PostComments()
{
return $this->hasMany('App\Models\Post\Comments', 'postId', 'id');
}
I also have a model called PostComments
I then have this relationship in there.
public function PostCommentsTags()
{
return $this->hasMany('App\Models\Posts\PostCommentTags', 'postCommentId', 'id');
}
Then in my controller I have
$post = Post::with(
'PostComments',
'PostComments.PostCommentsTags',
)->first();
This works wonderfully, I get a response like this.
{
title: 'This is a title',
description: 'Description',
comments:[
{
id: 1,
comment: 'this is a comment',
commentTags:[{
id: 1,
tagName: 'emotion',
tagValue: 'angry',
},
{
id: 10,
tagName: 'timeOfDay',
tagValue: 'morning',
}],
},
{
id: 2,
comment: 'this is a comment too',
commentTags:[{
id: 7,
tagName: 'emotion',
tagValue: 'happy',
},
{
id: 9,
tagName: 'timeOfDay',
tagValue: 'evening',
}],
},
{
id: 3,
comment: 'Too many comments now',
commentTags:[{
id: 12,
tagName: 'emotion',
tagValue: 'angry',
},
{
id: 14,
tagName: 'timeOfDay',
tagValue: 'evning',
}],
}
]
}
But I want to get rid of a lot of clutter, ie id. (the real thing is a lot more complex than this example, so has a lot of clutter and unneeded info)
So I have this below, which hides all the ids for comments, this works great!
$post->Comments->makeHidden(["id"]);
But how do I hide it for Comment tags too? (I want no ids in my json)
I have tried below, but it does not work? How do you access nested models when using the with statement. I can't seem to find anything to help.
$post->Comments->CommentsTags->makeHidden(["id"]);
Spent a bit to much time on this now so time to ask for help. Please help :)
Best wishes.
Argghh finally figured it out. If you want to remove unwanted data, you loop through it like this.
foreach($post->Comments as $comment){
$comment->CommentsTags->makeHidden(['id','created_at','updated_at']);
}
I guess its a nice way to have hidden attributes without having it the model.

Graphql: How can I solve the N + N problem?

After having implemented dataloader in the respective resolvers to solve the N+1 problem, I also need to be able to solve the N+N problem.
I need a decently efficient data loading mechanism to get a relation like this:
{
persons (active: true) {
id,
given_name,
projects (active: true) {
id,
title,
}
}
}
I've created a naive implementation for this, returning
{
persons: [
{
id: 1,
given_name: 'Mike'
projects: [
{
id: 1,
title: 'API'
},
{
id: 2,
title: 'Frontend'
}
]
}
{
id: 2,
given_name: 'Eddie'
projects: [
{
id: 2,
title: 'Frontend'
},
{
id: 3,
title: 'Testing'
}
]
}
]
}
In SQL the underlying structure would be represented by a many many to many relationship.
Is there a similiar tool like dataloader for solving this or can this maybe even be solved with dataloader itself?
The expectation with GraphQL is that the trip to the database is generally the fastest thing you can do, so you just add a resolver to Person.projects that makes a call to the database. You can still use dataLoaders for that.
const resolvers = {
Query: {
persons(parent, args, context) {
// 1st call to database
return someUsersService.list()
},
},
Person: {
projects(parent, args, context) {
// this should be a dataLoader behind the scenes.
// Makes second call to database
return projectsService.loadByUserId(parent.id)
}
}
}
Just remember that now your dataLoader is expecting to return an Array of objects in each slot instead of a single object.

GraphQL - Relationship returning null

I started to learn GraphQL and I'm trying to create the following relationship:
type User {
id: ID!,
name: String!,
favoriteFoods: [Food]
}
type Food {
id: ID!
name: String!
recipe: String
}
So basically, a user can have many favorite foods, and a food can be the favorite of many users. I'm using graphql.js, here's my code:
const Person = new GraphQLObjectType({
name: 'Person',
description: 'Represents a Person type',
fields: () => ({
id: {type: GraphQLNonNull(GraphQLID)},
name: {type: GraphQLNonNull(GraphQLString)},
favoriteFoods: {type: GraphQLList(Food)},
})
})
const Food = new GraphQLObjectType({
name: 'Food',
description: 'Favorite food(s) of a person',
fields: () => ({
id: {type: GraphQLNonNull(GraphQLID)},
name: {type: GraphQLNonNull(GraphQLString)},
recipe: {type: GraphQLString}
})
})
And here's the food data:
let foodData = [
{id: 1, name: 'Lasagna', recipe: 'Do this then that then put it in the oven'},
{id: 2, name: 'Pancakes', recipe: 'If you stop to think about, it\'s just a thin, tasteless cake.'},
{id: 3, name: 'Cereal', recipe: 'The universal "I\'m not in the mood to cook." recipe.'},
{id: 4, name: 'Hashbrowns', recipe: 'Just a potato and an oil and you\'re all set.'}
]
Since I'm just trying things out yet, my resolver basically just returns a user that is created inside the resolver itself. My thought process was: put the food IDs in a GraphQLList, then get the data from foodData usind lodash function find(), and replace the values in person.favoriteFoods with the data found.
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
description: 'Root Query',
fields: {
person: {
type: Person,
resolve(parent) {
let person = {
name: 'Daniel',
favoriteFoods: [1, 2, 3]
}
foodIds = person.favoriteFoods
for (var i = 0; i < foodIds.length; i++) {
person.favoriteFoods.push(_.find(foodData, {id: foodIds[i]}))
person.favoriteFoods.shift()
}
return person
}
}
}
})
But the last food is returning null. Here's the result of a query:
query {
person {
name
favoriteFoods {
name
recipe
}
}
}
# Returns
{
"data": {
"person": {
"name": "Daniel",
"favoriteFoods": [
{
"name": "Lasagna",
"recipe": "Do this then that then put it in the oven"
},
{
"name": "Pancakes",
"recipe": "If you stop to think about, it's just a thin, tasteless cake."
},
null
]
}
}
}
Is it even possible to return the data from the Food type by using only its ID? Or should I make another query just for that? In my head the relationship makes sense, I don't think I need to store the IDs of all the users that like a certain food in the foodData since it has an ID that I can use to fetch the data, so I can't see the problem with the code or its structure.
Calling shift and push on an array while iterating through that same array will invariably lead to some unexpected results. You could make a copy of the array, but it'd be much easier to just use map:
const person = {
name: 'Daniel',
favoriteFoods: [1, 2, 3],
}
person.favoriteFoods = person.favoriteFoods.map(id => {
return foodData.find(food => food.id === id)
})
return person
The other issue here is that if your schema returns a Person in another resolver, you'll have to duplicate this logic in that resolver too. What you really should do is just return the person with favoriteFoods: [1, 2, 3]. Then write a separate resolver for the favoriteFoods field:
resolve(person) {
return person.favoriteFoods.map(id => {
return foodData.find(food => food.id === id)
})
}

How to sort through two merged collections that have different models

I have two models Cities and States. One City has 1 State and one State can have 0 or more Cities. I need to retrieve all Cities and States separately because I need to display states even if a state has no related cities (like Alabama in the below example). The issue is I need to sort by State name first, and than by Cities in that state (if there are any)
Cities
id, state_id, name
1, 1, San Diego
2, 1, Hollywood
3, 2, Seattle
4, 3, Pheonix
States
id, name
1, California
2, Washington
3, Arizona
4, Alabama
Controller:
$cities = Cities::with('state')->get(); // Returns the state relationship
$states = States::get();
$merged = $states->merge($cities);
I would now like to sort by State name first, and then all the cities in that State and return a merged collection similar to this:
{
id: 4,
name: Alabama,
},
{
id: 3,
name: Arizona,
},
{
id: 3,
name: Pheonix,
state_id: 3
state: {
id: 3,
name: Arizona
}
},
{
id: 1,
name: California
},
{
id: 2,
name: Hollywood
state_id: 1,
state: {
id: 1,
name: California
}
},
{
id: 1,
name: San Diego,
state_id: 1,
state: {
id: 1,
name: California
}
},
{
id: 2,
name: Washington,
},
{
id: 2,
name: Seattle,
state_id: 2,
state: {
id: 2,
name: Washington
}
}
I think you could do a query like this:
$states = State::all()->sortBy('name'); // Here your sort by the state name first
If you made the relations right you can access the cities from each state like this: (you dont need to merge collections):
#foreach($states as $state) // this will sort the cities by id
{{$state->cities}}
#endforeach
To sort them by the name you could try this:
#foreach($states as $state)
{{$state->cities->sortBy('name')}}
#endforeach

Make requests for a list of objects in Graphql response

Let's say that I have the following query schema:
schema {
query: Query
}
type Query {
money(id: Long!): Money
coin(id: Long!): Coin
}
type Money {
id: Long!
coins: [Coin!]!
}
type Coin {
id: Long!
name: String!
history: [DateVal!]
}
type DateVal {
date: String!
val: Long!
}
I have two endpoints: /money and /coin, both of which take an id. When I make a request to /money I get back a response like below:
{
"id": 5,
"coins": [
{ "id" : 1, "name" : "N1", "history": null},
{ "id" : 2, "name" : "N2", "history": null},
]
}
If I call the coin endpoint with the coin id, I get the full history for the coin.
Is there a way to make GraphQL take a response like this and recursively call the /coin endpoint using the id of each coin in order to fill the history? I tried the following the following query but it didn't recursively call the coin endpoint.
{
money(id: 1) {
id
coins {
id
name
history {
date
val
}
}
}
}

Resources