How to sort through two merged collections that have different models - laravel

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

Related

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 groupBy and count the pivot table in laravel?

I have three tables,
User cities user_cities
id name title id name user_id city_id
1 aaa designer 1 cityA 2 1
2 bbb developer 2 cityB 2 2
3 ccc designer 3 cityC 1 2
1 1
2 3
3 2
After joining and querying the database my result is,
data: {
0: {
id: 1
name: aaa,
title: designer,
cities :[
0: {
id: 1,
name: cityA
}
1: {
id: 2,
name: cityB
}
2: {
id: 3,
name: cityC
}
]
},
1: {
id: 2
name: bbb,
title: developer,
cities :[
0: {
id: 1,
name: cityA
}
1: {
id: 2,
name: cityB
}
]
}
1: {
id: 3
name: ccc,
title: designer,
cities :[
0: {
id: 2,
name: cityB
}
]
}
}
Everything is fine until here. But i want to groupBy by cities and take count.
If i filter by title=designer the filtered data will display and i want to make group by cities and take count of it.
So my final result will be,
cityA = 1 counts
cityC = 2 counts
Help me to solve the problem.
You might use the withCount to eager load the relationship counts.
I assume you already have the cities relationship defined in the User.php model:
class User
{
public function cities()
{
return $this->belongsToMany(City::class);
}
}
Then:
$users = User::withCount('cities')->get();
I think the results are gonna be:
data: {
0: {
id: 1
name: aaa,
title: designer,
cities_count: 3
},
1: {
id: 2
name: bbb,
title: developer,
cities_count: 2
}
1: {
id: 3
name: ccc,
title: designer,
cities_count: 1
}
}
An alternative could be using lazy eager loading:
$users = User::all()->loadCount('cities');

Merging Rest datasets in Federation with resolvers?

pretty new to GraphQL and Apollo Federation.
I have a question, is it possible to populate one dataset with another such as:
# in Shop Service
type carId {
id: Int
}
type Shop #key(fields: "id") {
id: ID!
name: String
carIds: [CarId]
}
# in Car Service
type Car {
id: ID!
name: String
}
extends type Shop #key(fields: "id") {
id: ID! #external
cars: [Car]
}
Car Resolver
Query{...},
Shop: {
async cars(shop, _, { dataSources }) {
console.log(shop); // Issue here is it returns the references that are an object only holding the `id` key of the shop, I need the `cars` key here, to pass to my CarsAPI
return await dataSources.CarsAPI.getCarsByIds(shop.carsIds);
}
}
From the Shop rest api the response would look like:
[{id: 1, name: "Brians Shop", cars: [1, 2, 3]}, {id: 2, name: "Ada's shop", cars: [4,5,6]}]
From the Car rest api the response would look like:
[{id: 1, name: "Mustang"}, {id: 2, name: "Viper"}, {id: 3, name: "Boaty"}]
So what I want to archive is to query my GraphQL server for:
Shop(id: 1) {
id
name
cars {
name
}
}
And then expect:
{
id: 1,
name: "Brian's shop",
cars: [
{name: "Mustang"},
{name: "Viper"},
{name: "Boaty"}
]
}
Is this possible, it was what I thought when I chose federation :)
So, if I understand correctly after your comments, what you want is to have the carIds from Shop service coming in your Car service inside the cars resolver.
You can make use of the #requires directive which will instruct Apollo Server that you need a field (or a couple of) before it starts executing the cars resolver. That is:
Car Service
extend type Shop #key(fields: "id") {
id: ID! #external
carIds: [Int] #external
cars: [Car] #requires(fields: "carIds")
}
Now, inside the cars resolver, you should be able to access shop.carIds on your first parameter.
See: https://www.apollographql.com/docs/apollo-server/federation/advanced-features/#computed-fields

How to set up eloquent relationships to query table where relationship between two tables exists in a third table

I'm using Laravel 5.7 and mysql. I need help setting up my eloquent relationships between 3 tables.
Locations
id
city
state
Services
id
name
Allowed_Locations
location_id (id from Locations table)
service_id (id from Services table)
Locations
id, city, state
1, San Diego, California
2, Seattle, Washington
3, Portland, Oregon
Services
id, name
1, Tax Services
2, Legal Services
Allowed Locations
location_id, service_id
1, 1
1, 2
3, 2
Basically one service can be available in many locations. I want to be able to query services, along with the locations the service is available in
Heres an example of what I am looking to return. I know how to do this using MySQL but I'm not sure how to set up eloquent relationships
{
{
id: 1,
name: Tax Services
locations:
{
{
id: 1,
city: San Diego,
state: California,
},
{
id: 2,
city: Seattle,
state: Washington,
},
{
id: 3,
city: Seattle,
state: Washington,
},
}
},
{
id: 2,
name: Legal Services
locations:
{
{
id: 3,
city: Portland,
state: Oregon,
},
}
}
}
You can do this also
In Service Model,
public function locations() {
return $this->belongsToMany('App\Locations', 'allowed_locations');
}
You need to pass 'service_id' as third and 'location_id' as forth arguments if you have different primary key in Services and Locations table.
I was able to figure out
public function locations() {
return $this->belongsToMany('App\Locations', 'allowed_locations', 'service_id','location_id');
}
there is many to many relationship between location and service so you just need to read Laravel's many to many relation section and add some functions to your models also migration for your pivot table.
https://laravel.com/docs/5.7/eloquent-relationships#many-to-many
make this relationship in your service eloquent
public function locations() {
return $this->belongsToMany('App\Location', 'allowed_locations',
'service_id','location_id');
}
and in controller use this query to get all services with multiple location
$services = Service::with('locations')->get();

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

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

Resources