How to load relations with tree entity with typeorm? - typeorm

I have a Role Entity and a Route Entity which is tree structure, and they're ManyToMany relation.
Now I want to retrive all the roles via RoleRepository.find({relations: ['routes']}), which will load all the roles data as expected, however the routes prop won't automaticlly load it's children data, which looks like:
[{
id: 1,
name: 'route1',
routes: [{
id: 1,
path: '/home'
}]
}]
I've checked all documentation and had no clue to make it.
#Entity()
export class Role {
#PrimaryGeneratedColumn()
public id: number
... some other columns
#ManyToMany(type => Route, route => route.roles)
public routes: Route[]
}
#Entity()
#Tree('materialized-path')
export class Route {
#PrimaryGeneratedColumn()
public id: number
#TreeParent()
public parent: Route
#TreeChildren({ cascade: true })
public children: Route[]
#ManyToMany(type => Role, role => role.routes)
#JoinTable()
public roles: Role[]
}

I think the correct way to get the relations when is a tree entity is making:
await getManager().getTreeRepository(Route).findTrees({ relations: ["roles"] });

To load the children of a relation you can just add the property you want within the relations array like this:
RoleRepository.find({relations: ['routes', 'routes.roles']})
it should give you something like that:
"route": {
"id": 1,
"name": "route1",
"roles": [{
"id": 1,
"name": "role1",
"routes": [{
"id": 1,
"name": "route1"
}, {
"id": 2,
"name": "route2"
}
]
}
]
}

Related

OData GroupBy and Select

Expecting the following example table CustomerOrders
Id
CustomerId
Customer
Product
1
1
Alice
Pizza
2
1
Alice
Pasta
3
2
Bob
Burger
In C# I'm was able to use the following Linq query to produce a nice List<Customer> result with a nested orders collection for every customer:
List<CustomerOrders> queryResult = GetCustomerOrders();
return queryResult
.GroupBy(x => x.CustomerId)
.Select(x => new Customer
{
Id = x.First().CustomrId,
Customer = x.First().Customer,
Orders = x.ToList()
})
.ToList();
Now I want to achive this result directly over an odata query in the client application to get the following JSON result:
[
{
"id": 1,
"customer": Alice,
"orders": [ "Pizza", "Pasta" ]
},
{
"id": 2,
"customer": Bob,
"orders": [ "Burger" ]
}
]
Is there a way to transfer this query in odata?
GroupBy in OData is similar to SQL, only the aggregates and common columns are returned, we lose access to the individual items, so we can return a grouping and a count of the orders, using group by, but not the array of orders.
If your schema has a Customer entity and there is a collection navigation property from Customer to Orders, then we do not need to use grouping at all:
~/Customers?$expand=Orders($select=Product)&$select=Id,Name
The output is structured in a slightly similar manner and should resemble something like this:
{
"#odata.context": "~/$metadata#Customers(Id,Name,Orders(Product))",
"value": [
{
"Id": 1,
"Name": "Alice",
"Orders": [{"Product": "Pizza"},
{"Product": "Pasta"}]
},
{
"Id": 2,
"Name": "Bob",
"Orders": [{"Product": "Burger"}]
}
]
}
A key concept in OData is that the shape of the overall graph should not be modified, it is designed deliberately to always maintain the structure of the Entities that are returned. This means that the definition document is always correct, the only thing missing from this response is the additional fields that were not requested.
If you need the output in the client specifically as mentioned, then you can expose that as a custom function on the controller:
[EnableQuery]
public IQueryable<CustomerSummary> GetCustomersWithOrderSummary()
{
List<CustomerOrders> queryResult = GetCustomerOrders();
return queryResult
.GroupBy(x => x.CustomerId)
.Select(x => new CustomerSummary
{
Id = x.Key,
Customer = x.First().Customer,
Orders = x.Select(o => o.Product)
});
}
If using GroupBy, the closest response we can get is this:
~/CustomerOrders?$apply=groupby((CustomerId,Customer),aggregate($count as Orders))
But here we will return a count of the orders, and not an array of the product values as expected:
{
"#odata.context": "~/$metadata#CustomerOrders(CustomerId,Customer,Orders)",
"value": [
{
"#odata.id": null,
"CustomerId": 1,
"Customer": "Alice",
"Orders": 2
},
{
"#odata.id": null,
"CustomerId": 2,
"Customer": "Bob",
"Orders": 1
}
]
}

Returning tasks for each session in Neo4J (list of lists)

I'm using Neo4J for a mentor platform I'm building and I'm stumped by the following:
Given the following nodes and properties:
Mentor{ login, ... }
Mentee{ login, ... }
Session{ notes, ... }
Task{ complete, name }
And the following associations:
// each session has 1 mentor and 1 mentee
(Mentor)<-[:HAS]-(Session)-[:HAS]->(Mentee)
// each task is FOR one person (a Mentor or Mentee)
// each task is FROM one Session
(Session)<-[:FROM]-(Task)-[:FOR]->(Mentor or Mentee)
What's the best way to query this data to produce an API response in the following shape? Similarly, is this a reasonable way to model the data? Maybe something with coalesce?
{
mentor: { login: '...', /* ... */ },
mentee: { login: '...', /* ... */ },
sessions: [
{
notes,
/* ... */
mentorTasks: [{ id, name, complete }],
menteeTasks: [{ id, name, complete }]
]
I first tried:
MATCH (mentor:Mentor{ github: "mentorlogin" })
MATCH (session:Session)-[:HAS]->(mentee:Mentee{ github: "menteelogin" })
OPTIONAL MATCH (mentor)<-[:FOR]-(mentorTask:Task)-[:FROM]->(session)
OPTIONAL MATCH (mentee)<-[:FOR]-(menteeTask:Task)-[:FROM]->(session)
RETURN
mentor,
mentee,
session,
COLLECT(DISTINCT mentorTask) as mentorTasks,
COLLECT(DISTINCT menteeTask) as menteeTasks
ORDER BY session.date DESC
But that's janky - The mentor and mentee data is returned many times, and it's completely gone if the mentee has no sessions.
This seems more appropriate, but I'm not sure how to fold in the tasks:
MATCH (mentor:Mentor{ github: "mentorlogin" })
MATCH (mentee:Mentee{ github: "menteelogin })
OPTIONAL MATCH (session:Session)-[:HAS]->(mentee)
OPTIONAL MATCH (mentor)<-[:FOR]-(mentorTask:Task)-[:FROM]->(session)
OPTIONAL MATCH (mentee)<-[:FOR]-(menteeTask:Task)-[:FROM]->(session)
RETURN
mentor,
mentee,
COLLECT(DISTINCT session) as sessions
EDIT: Working! thanks to a prompt response from Graphileon. I made a few modifications:
changed MATCH statement so it returns the mentor and mentee even if there are no sessions
sort sessions by date (most recent first)
return all node properties, instead of whitelisting
MATCH (mentor:Mentor{ github: $mentorGithub })
MATCH (mentee:Mentee{ github: $menteeGithub })
RETURN DISTINCT {
mentor: mentor{ .*, id: toString(id(mentor)) },
mentee: mentee{ .*, id: toString(id(mentee)) },
sessions: apoc.coll.sortMaps([(mentor:Mentor)<-[:HAS]-(session:Session)-[:HAS]->(mentee:Mentee) |
session{
.*,
id: toString(id(session)),
mentorTasks: [
(session)<-[:FROM]-(task:Task)-[:FOR]->(mentor) |
task{ .*, id: toString(id(task)) }
],
menteeTasks: [
(session)<-[:FROM]-(task:Task)-[:FOR]->(mentee) |
task{ .*, id: toString(id(task)) }
]
}
], "date")
} AS result
Presuming you would have these data:
You can do something along these lines, with nested pattern comprehensions
MATCH (mentor:Mentor)<-[:HAS]-(:Session)-[:HAS]->(mentee:Mentee)
RETURN DISTINCT {
mentor: {id:id(mentor), name: mentor.name},
mentee: {id:id(mentee), name: mentee.name},
sessions: [(mentor:Mentor)<-[:HAS]-(session:Session)-[:HAS]->(mentee:Mentee) |
{ id: id(session),
name: session.name,
mentorTasks: [(session)<-[:FROM]-(task:Task)-[:FOR]->(mentor) |
{id:id(task), name: task.name}
],
menteeTasks: [(session)<-[:FROM]-(task:Task)-[:FOR]->(mentee) |
{id:id(task), name: task.name}
]
}
]
} AS myResult
returning
{
"mentor": {
"name": "Mentor Jill",
"id": 211
},
"sessions": [
{
"menteeTasks": [
{
"id": 223,
"name": "Task D"
},
{
"id": 220,
"name": "Task C"
},
{
"id": 219,
"name": "Task B"
}
],
"name": "Session 1",
"id": 208,
"mentorTasks": [
{
"id": 213,
"name": "Task A"
}
]
}
],
"mentee": {
"name": "Mentee Joe",
"id": 212
}
}
Note that using the pattern comprehensions, you can avoid the OPTIONAL matches. If a pattern comprehension does not find anything, it returns []

get parent object filter by nested child fields

Inside teacher, there is child subject, and subject has a child student. I want to get all teachers filter by student_name. Teacher object can be something like this:
{ "id": 5,
"name": "teacher-one",
"gender": "male",
"subject": {
"id": 10,
"subject_name": "Maths",
"student": {
"id": 1,
"student_name": "student-one",
"grade" : "one",
}
}
}
I am looking to filter this out by using find method, something like
Teacher.find(:all, params: {gender: "male'}) which returns all the male teachers. But the following code does not work:
Teacher.find(:all, params: { subject: { student: grade == "one" }})
Try this:
Teacher.joins(subject: :student).where(student: {student_name: "Bala"})

Embedded object in loopback model schema for Swagger

In the context of a remote method, I'm trying to define a model schema of a parameter passed in the body. This object looks like this:
{
name: "Alex",
credentials: {
user: "alex",
pass: "pass"
}
}
So, I have this code in my remote method definition:
MyModel.remoteMethod("postSomething", {
accepts: [
{arg: 'person', type: {
"name": "string",
"credentials": {
"type": "object",
"properties": {
"user": "string",
"pass: "string"
}
}
}, http: {source: 'body'}, required: true
}
],
.....
Unfortunatelly, the details of this embedded object (credentials) are not shown in the generated Swagger explorer. This is what I see:
{
"user": "string",
"credentials": {}
}
I've tried many different ways but I could not show the properties of the credentials object.
Any ideas?
Loopback 2.x
Edit: Note the following only works for Loopback 2.x, as the type registry changed in 3.x.
The problem is that the data you are providing needs to be on the type property for the nested value. This should work:
MyModel.remoteMethod('postSomething', {
accepts: [
{
arg: 'person',
type: {
name: 'string',
credentials: {
type: {
user: 'string',
pass: 'string'
}
}
},
http: {
source: 'body'
},
required: true
}
],
//...
This also works with arrays:
accepts: [
{
arg: 'Book',
type: {
title: 'string',
author: 'string',
pages: [{
type: {
pageNo: 'number',
text: 'string'
}
}]
}
}
],
// ...
Loopback 3.x
Since the model registry and strong remoting changed in Loopback 3.x to only allow string or array types, you can't really avoid creating a new model. If you would like to quickly 'inline' a model without going through the full process of adding the model json file, adding it to model-config.json etc. you can register it directly on the app:
app.registry.createModel('Person', {
firstName: 'string',
lastName: 'string'
}, { base: 'Model' });
You can set the base to one of your other models if you want to extend an existing model (e.g, add another property that is only accepted in the given remote method)
If you want to create the model without cluttering up your model registry, you can do so by calling createModel on loobpack itself:
const loopback = require('loopback')
const modl = loopback.createModel({
name: 'Person',
base: null,
properties: {
firstName: {
type: 'string',
id: true // means it won't have an id property
}
}
});
In both of the above examples, you refer to the model by name to attach it to the remote method:
accepts: [
{
arg: 'Person',
type: 'Person'
}
],
// ...
Note you will need to create a sub-model for every sub-property (e.g. credentials)
Loopback swagger only picks up the outer object ignoring the properties of the object.
If you want to show a nested object in the swagger docs for the request body, you will have to make nested model.
Assuming you have a model called as person. You have to create another model named "credentials" having properties user and password. Then define the relationship in your person model's config
{
"name": "Person",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"name": {
"type": "string",
"required": true
}
},
"validations": [],
"relations": {
"credentials": {
"type": "embedsOne",
"model": "credentials",
"property": "credentials",
"options": {
"validate": true,
"forceId": false
}
}
},
"acls": [],
"methods": {}
}
And add a reference to this model where you define your remote method
MyModel.remoteMethod("postSomething", {
accepts: [
{arg: 'person', type: {Person},
http: {source: 'body'}, required: true
}
],
To avoid "Treating unknown remoting type" warning make sure your model is marked as "public" inside your "model-config.json"

Additional attributes in N-N mongoid (Like in pivot table)

I have a has_many_and_belongs_to relationship between central and coordinators.
So my mongo documents are represented as below:
central = {
_id: 1,
title: 'First Name',
coordinators: [
BSON[1],
BSON[2],
BSON[3]
]
}
coordinators = [
{
_id: 1,
name: 'Me',
centrals: [BSON[1], BSON[2]]
},
{
_id: 1,
name: 'Mateus'
centrals: [BSON[1]]
},
{
_id: 1,
name: 'Gomes'
centrals: [BSON[1]]
},
]
If I do this:
#central = Central.find(1)
#coordinator = #central.coordinators.find(1)
#coordinator.can_edit = false
It will apply to the coordinators document resulting in this:
coordinator = {
_id: 1,
name: 'Me',
centrals: [BSON[1], BSON[2]],
can_edit: false
}
But what I really want to do is apply this can_edit attribute in the relationship, like in pivot table in RDBMS:
central = {
_id: 1,
titulo: 'First Name',
coordinators: [
{
_id: 1,
name: 'Me',
can_edit: false
},
BSON[2],
BSON[3]
]
}
Only for the central with id 1 I want to aply the can_edit to false.
I have to keep the relation between a Central and Coordinator, but in some situation, I want to have an additional information about that relation, like if I would not allow a coordinator to edit some data only in central with id 1.
How can I do this using mongoid?
The solution for this was create another relation N-N:
Add on central.rb
has_and_belongs_to_many :blocked_coordenadors,
class_name: "Central",
inverse_of: :blocked_centrals
And in coordinator.rb:
has_and_belongs_to_many :blocked_centrals,
class_name: "Central",
inverse_of: :blocked_coordenadors
And to check I do this:
central.blocked_coordenadors.include? coordinator

Resources