How can I have a Sequelize composite key a (non-unique, unique) pair and then use it to map associations? - express

I have these relations in my SQL database. There are two entity types A and B. These entities are owners of a resource and I manage this via a mapping table. The relations are:
Entity A has one or more Owner entries.
Entity B has one or more Owner entries.
Each Owner entry has zero or one Entity A entry.
Each Owner entry has zero or one Entity B entry.
Each Resource has zero or more owners.
Each Owner entry tracks one resource.
I am using this to model individuals and groups owning a resource. The problem is when I go to use the ORM it complains that I have not given an association between Owners and Entities. I'm not really sure how to do this with Sequelize. What I would like to do is have a composite key like so:
(entity_type, entity_id)
where entity_type is from enum (EntityA, EntityB) and entity_id is an id corresponding to a record in either of those tables. In this way I can guarantee that the composite key is unique. But again, I'm not really sure how I can implement this via Sequelize.

While make associate, we can use where condition as below code.
Model : Owner.js
"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class Owner extends Model {
static associate(models) {
// define association here
this.belongsTo(models.EntityA, {
foreignKey: "entity_id",
as: "A",
where: { entity_type: 0 }
});
this.belongsTo(models.EntityB, {
foreignKey: "entity_id",
as: "B",
where: { entity_type: 1 }
});
}
}
Owner.init({
entity_type: DataTypes.INTEGER,
entity_id: DataTypes.INTEGER,
}, {
sequelize,
modelName: "Owner"
});
return Owner;
};
db.Owner.findAll({
include: 'A', 'B'
});

Related

TypeORM How to access reference relation field without loading relation entity

As we know, to create ManyToOne/OneToMany relation we have to use #ManyToOne/#OneToMany decorators on a field.
In my project I have two entities: Project and Position.
This is how I created a relation:
#Entity('positions')
export class Position {
#ManyToOne(() => Project, {
nullable: false,
eager: true,
})
#JoinColumn()
project: Project;
}
TypeORM documentation says this code will create projectId FOREIGN KEY column in the database and store a project id in it.
Then when we trying to access project property TypeORM loads a project by the id stored in projectId field.
QUESTION
How can I access that pojectId field without loading a relational entity?
The property projectId does not exists by default in Position entity and if I manually create it it is not populated by projectId column value.
I have tried this way:
#ManyToOne(() => Project, {
nullable: false,
eager: false,
})
#JoinColumn()
project: Project;
projectId: string;
You can use the #RelationId decorator exported by typeorm. Using your example:
import {
Column,
Entity,
ManyToOne,
RelationId,
JoinColumn,
} from 'typeorm'
#Entity()
export class Position {
#ManyToOne(() => Project, {
nullable: false,
eager: false,
})
#JoinColumn()
project: Project;
#Column()
#RelationId((position: Position) => position.project)
projectId: string;
}

TypeORM cascade option: cascade, onDelete, onUpdate

Do cascade options in TypeORM overlap or do they have a completely different purpose? Their description in the documentation is very scarce and partly missing, or I couldn't find it.
IOW, do the following options
{ cascade: "update" } = { onUpdate: 'CASCADE' }
{ cascade: "remove" } = { onDelete: 'CASCADE' }
have the same effect?
Or the cascade option is only for the TypeORM use while onUpdate and onDelete are only for the DB schema (created by migration)?
This is my conclusion of looking into it:
The cascade option does not affect the database column constraints, and I believe is used by TypeORM only in evaluating how to save entity relations to the database. We can define entities like this:
#Entity()
class Book extends BaseEntity {
#ManyToOne(() => Author, (author) => author.books, {
onDelete: 'CASCADE',
})
public author?: Author
}
#Entity()
class Author extends BaseEntity {
#OneToMany(() => Book, (book) => book.author, {
cascade: true,
})
public books: Book[];
}
onDelete sets the authorId foreign key to CASCADE onDelete on Book. This means that when the author is deleted, the book is also deleted.
Setting cascade: true on Author tells TypeORM that if a new book is appended on an author and the author is saved, the new book should also be saved to the database. Like this:
const author = await Author.findOne({ id: '123' });
author.books.push(new Book(...));
await author.save();
If cascade is not set on Book, the new book will not be saved to the database.
onDelete: 'CASCADE isn't supported in OneToMany relations yet. More context here: https://github.com/typeorm/typeorm/issues/1913

GraphQL, Dataloader, [ORM or not], hasMany relationship understanding

I'm using for the first time Facebook's dataloader (https://github.com/facebook/dataloader).
What I don't understand is how to use it when I have 1 to many relationships.
Here it is a reproduction of my problem: https://enshrined-hydrant.glitch.me.
If you use this query in the Playground:
query {
persons {
name
bestFriend {
name
}
opponents {
name
}
}
}
you get values.
But if you open the console log here: https://glitch.com/edit/#!/enshrined-hydrant you can see these database calls I want to avoid:
My Person type is:
type Person {
id: ID!
name: String!
bestFriend: Person
opponents: [Person]
}
I can use dataloader good for bestFriend: Person but I don't understand how to use it with opponents: [Person].
As you can see the resolver has to return an array of values.
Have you any hint about this?
You need to create batched endpoints to work with dataloader - it can't do batching by itself.
For example, you probably want the following endpoints:
GET /persons - returns all people
POST /bestFriends, Array<personId>` - returns an array of best friends matchin the corresponding array of `personId`s
Then, your dataloaders can look like:
function batchedBestFriends(personIds) {
return fetch('/bestFriends', {
method: 'POST',
body: JSON.stringify(personIds))
}).then(response => response.json());
// We assume above that the API returns a straight array of the data.
// If the data was keyed, you could add another accessor such as
// .then(data => data.bestFriends)
}
// The `keys` here will just be the accumulated list of `personId`s from the `load` call in the resolver
const bestFriendLoader = new DataLoader(keys => batchedBestFriends(keys));
Now, your resolver will look something like:
const PersonType = new GraphQLObjectType({
...
bestFriend: {
type: BestFriendType,
resolve: (person, args, context) => {
return bestFriendLoader.load(person.id);
}
}
});

Load only the data that's needed from database with Graphql

I'm learning graphql and I think I've spot one flaw in it.
Suppose we have schema like this
type Hero {
name: String
friends: [Person]
}
type Person {
name: String
}
and two queries
{
hero {
name
friends {
name
}
}
}
and this
{
hero {
name
}
}
And a relational database that have two corresponding tables Heros and Persons.
If my understanding is right I can't resolve this queries such that for the first query the resulting sql query would be
select Heros.name, Persons.name
from Heros, Persons
where Hero.name = 'Some' and Persons.heroid = Heros.id
And for the second
select Heros.name, Persons.name from Heros
So that only the fields that are really needed for the query would be loaded from the database.
Am I right about that?
Also if graphql would have ability to return only the data that's needed for the query, not the data that's valid for full schema I think this would be possible, right?
Yes, this is definitely possible and encouraged. However, the gist of it is that GraphQL essentially has no understanding of your storage layer until you explicitly explain how to fetch data. The good news about this is that you can use graphql to optimize queries no matter where the data lives.
If you use javascript, there is a package graphql-fields that can simplify your life in terms of understanding the selection set of a query. It looks something like this.
If you had this query
query GetCityEvents {
getCity(id: "id-for-san-francisco") {
id
name
events {
edges {
node {
id
name
date
sport {
id
name
}
}
}
}
}
}
then a resolver might look like this
import graphqlFields from 'graphql-fields';
function getCityResolver(parent, args, context, info) {
const selectionSet = graphqlFields(info);
/**
selectionSet = {
id: {},
name: {},
events: {
edges: {
node: {
id: {},
name: {},
date: {},
sport: {
id: {},
name: {},
}
}
}
}
}
*/
// .. generate sql from selection set
return db.query(generatedQuery);
}
There are also higher level tools like join monster that might help with this.
Here is a blog post that covers some of these topics in more detail. https://scaphold.io/community/blog/querying-relational-data-with-graphql/
In Scala implementation(Sangria-grahlQL) you can achieve this by following:
Suppose this is the client query:
query BookQuery {
Books(id:123) {
id
title
author {
id
name
}
}
}
And this is your QueryType in Garphql Server.
val BooksDataQuery = ObjectType(
"data_query",
"Gets books data",
fields[Repository, Unit](
Field("Books", ListType(BookType), arguments = bookId :: Nil, resolve = Projector(2, (context, fields) =>{ c.ctx.getBooks(c.arg(bookId), fields).map(res => res)}))
)
)
val BookType = ObjectType( ....)
val AuthorType = ObjectType( ....)
Repository class:
def getBooks(id: String, projectionFields: Vector[ProjectedName]) {
/* Here you have the list of fields that client specified in the query.
in this cse Book's id, title and author - id, name.
The fields are nested, for example author has id and name. In this case author will have sequence of id and name. i.e. above query field will look like:
Vector(ProjectedName(id,Vector()), ProjectedName(title,Vector()),ProjectedName(author,ProjectedName(id,Vector()),ProjectedName(name,Vector())))
Now you can put your own logic to read and parse fields the collection and make it appropriate for query in database. */
}
So basically, you can intercept specified fields by client in your QueryType's field resolver.

Setting foreignKey of Ember Data model

It looks like during Revision 3 of Ember Data 1, you could set the foreign key:
App.Model = DS.Model.extend({
namingConvention: {
// LOUD NAMING CONVENTION
// Changes fooKey to FOOKEY
keyToJSONKey: function(key) {
return key.toUpperCase();
},
// Determines the name of foreign keys in
// belongsTo relationships
foreignKey: function(key) {
return key.toUpperCase()+"_ID";
}
}
});
That does not seem to work now (currently Revision 7). How do you set the foreign key?
You may be looking for keyForBelongsTo. See http://emberjs.com/api/data/classes/DS.RESTSerializer.html
App.ApplicationSerializer = DS.RESTSerializer.extend({
keyForBelongsTo: function(type, name) {
return this.keyForAttributeName(type, name) + "_id";
},
});