Short question: is it possible to model a json with a class in TS (/angular) ?
Example:
I'm using Firebase and I have a node /books which looks like:
books
-- 157sq561sqs1
-- author: 'Foo'
-- title: 'Hello world'
(157sq561sqs1 here is the id of the book)
it will looks:
157sq561sqs1 : { author: 'Foo', title:'Hello world'}
I tried to model that with:
export class Book {
id: number;
author: string;
title: string;
}
But it will looks like:
books
-- 0
-- id: 157sq561sqs1
-- author: 'Foo'
-- title: 'Hello world'
Is it possible to model something like that in order to have the model shown ?
I think it is not possible directly with typescript.You have to write code for that.
Related
I am using NestJS + typescript. This is my entity:
#Entity()
export class YearCourse {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#ManyToMany(() => User)
#JoinTable()
admins: User[];
}
And now given user I would like to find all yearCourse entities where given user is it's admin.
Prototype looks like this:
async getByUser(user: User): Promise<YearCourse[]> {
return this.yearCourseRepository.find({where: { admins.contains(user) }})
}
This obviously doesn't work. How to achieve this type of search?
To write more advanced SQL queries with TypeOrm, you can use the Query Builder.
Considering your admin id is stored as adminId (admin_id) in the User entity, it should look like this:
// query builder name ('year') is completely customisable
return this.yearCourseRepository.createQueryBuilder('year')
// load "admins" relation (user entity) and select results as "admins"
.leftJoinAndSelect('year.admins', 'admins')
// search where users have the "user.id" as "adminId"
.where('admins.adminId = :id', { id: user.id })
// get many results
.getMany();
I recommend you to write this directly in your repository file when using Query Builder for more cleanliness
everybody! I have two related tables, and one of them contains column with id of the second table's record. So I want to extract several properties from the second table and сoncatenate them with the result of the first one.
I faced the problem which is when I try to make a query via SQL string(that is SELECT * FROM `characters` `c` INNER JOIN `money` `m` ON `c`.`bank` = `m`.`id` WHERE uuid = ?) — everything works fine. But when I do the same things via TypeORM the queryBuilder returns only data from the first table (characters).
My chatacters Entity:
export default class Character extends BaseEntity {
#PrimaryGeneratedColumn()
public id: number;
#Column()
uuid: number;
...
#OneToOne(() => Money)
#JoinColumn()
bank: Money;
...
public static async findByUuid(uuid: number): Promise<Character> {
return await getManager()
.createQueryBuilder(Character, "c")
.innerJoinAndSelect(Money, "m", "c.bank = m.id")
.where("uuid = :uuid ", { uuid })
.getOne();
}
}
My money Entity:
export default class Money extends BaseEntity {
#PrimaryGeneratedColumn()
public id: number;
#Column()
type: number;
#Column()
holder: string;
#Column()
balance: number;
}
I tried to replace c.bank = m.id with c.bankId = m.id, but it also didn't work. I tried to remove #OneToOne relation, but I got the same effect. I turned "logging" option on and copied SQL string generated, then I executed it via PhpMyAdmin and ... It worked! It seems TypeORM modifies query's result and gives me the row of the first table only. I don't understand what goes wrong. Please, help.
You should be able to do this in two ways:
1. Using QueryBuilder (like you tried):
const character = await getManager()
.createQueryBuilder(Character, "c")
.innerJoinAndSelect("c.bank", "bank")
.where("c.uuid = :uuid ", { uuid })
.getOne();
return character;
2. Using findOne:
const character = await getManager()
.getRepository(Character)
.findOne({ relations: ["bank"], where: { uuid } });
return character;
Now you should be able to access bank using character.bank
Update: For removing the foreign key bankId from characters table, you can do something like below.
#OneToOne(() => Money, { createForeignKeyConstraints: false })
bank: Money;
See the reference from typeorm documentation.
As you said, if some other server is trying to save id into bank field, that would be problematic because your Character entity's bank property is of type Money. So better if you change the name of the relation to something else to avoid conflict.
Hope this helps. Cheers 🍻 !!!
I have below interface.
interface ProductJson {
id: number;
name: string;
price: number;
}
I want to have multiple alias names for price, like price and alias names: rate, etc. How can I read json data attribute 'rate' for 'price' and also read 'price' too.
You can use a custom serializer to create aliases between fields:
Example using #kaiu/serializer:
class ProductJson {
id: number;
name: string;
#FieldName('rate')
price: number;
}
Or you can also create a getter method and use the serializert to map your JSON to a class instance in order to use your method directly, see https://kaiu-lab.github.io/serializer/ for in-depth stuff.
One way is to maintain a group of attribute names that you want to alias.
And then add the interface property price to the json itself, if it contains the aliased properties like rate or amount.
Now you can simply access price from else where, which should give the same value
Ex:
var price_group = ['price', 'rate', 'amount'];
var some_other_group = []
var resp = {rate: 200, p: 12}
var resp2 = {price: 300, p: 12};
Object.keys(resp).forEach(key => {
if(price_group.indexOf(key) > -1){
resp.price = resp[key]
}
});
console.log(resp.price)
Object.keys(resp2).forEach(key => {
if(price_group.indexOf(key) > -1){
resp.price = resp[key]
}
});
console.log(resp2.price)
I'm not sure you can do that tbh.
You can easily do it by programming your stuff that reads/writes the json to accept stuff like rate, price, moolah and just write it as
{
price: number
}
edit: what i'm saying is you take the user input or any other input that specifies something like {moolah: 30} and you take that '30' and put it on {price: 30} in your json.
I have a model City:
class City
belongs_to :country
end
And a model Street:
//street has an attribute `Type` which can be 1, 2 or 3
class Street
belongs_to City
end
I want all cities in Croatia including streets that are of type 2
So something like this:
cities = City.find_by_country("Croatie").include_streets_where(type: 2)
so I get something like this:
[
{
name: "Zagreb",
country: "Croatia",
streets: [{name: "street1", type: 2},{name: "street2", type: 2}]
},
{
name: "Split",
country: "Croatia",
streets: [{name: "street3", type: 2},{name: "street4", type: 2}]
}
]
My solution is to first get the cities by country name and loop through each city to query it's streets. But I'm guessing there is a better way.
I'm assuming your City has_many :streets, and your Country class has an attribute name.
A 2-layered loop is not as efficient as an INNER JOIN, which you can assemble with this: (You can look at the SQL it generates by appending .to_sql to the end of it.)
cities = City.where(country: Country.find_by_name("Croatie"))
.joins(:streets).where(streets: { type: 2 })
This will return a list of city objects matching your criteria. Now to get it to the format you specified, you have to do some formatting on the Ruby side since the default returned is not an Array type. This is assuming you want an array of hashes.
formatted_list = cities.map do |city|
{ name: city.name,
country: city.country.name,
streets: list_of_streets_with_type(city.streets) }
end
def list_of_streets_with_type(streets)
streets.map do |street|
{ name: street.name,
type: street.type }
end
end
In the end, formatted_list would be returning what you wanted.
(Disclaimer: I have not checked syntax, but the general idea is there. Give it a try, it should work)
I have just started using TypeORM and I'm struggling getting the following relationship to work:
User->Friends, whereas a Friend is also a User Object.
My getters, getFriends & getFriendsInverse are working, however; I do now want to distinguish between the two. In other words; when I perform a mysql join I do not want to do a left join on friends and another one on inverseFriends.
The getter getFriends() needs to return all friends, regardless of which "side" the object I'm on.
Does that make sense?
This is my model definition:
getFriends() {
// This method should also return inverseFriends;
// I do not want to return the concat version; this should
// happen on the database/orm level
// So I dont want: this.friends.concat(this.inverseFriends)
return this.friends;
}
#ManyToMany(type => User, user => user.friendsInverse, {
cascadeInsert: false,
cascadeUpdate: false,
})
#JoinTable()
friends = [];
#ManyToMany(type => User, user => user.friends, {
cascadeInsert: true,
cascadeUpdate: true,
cascadeRemove: false,
})
friendsInverse = [];
I hope someone understands my question :D
Thanks
Matt
You can self-reference your relations. Here is an example of a simple directed graph (aka a node can have a parent and multiple children).
#Entity()
export class Service extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
#Index({ unique: true })
title: string;
#ManyToOne(type => Service, service => service.children)
parent: Service;
#OneToMany(type => Service, service => service.parent)
children: Service[];
}
An important note to keep in mind is that these relations are not auto loaded when reading an object from the DB with find* functions.
To actually load them, you have to use query builder at the moment and join them. (You can join multiple levels.) An example:
let allServices = await this.repository.createQueryBuilder('category')
.andWhere('category.price IS NULL')
.innerJoinAndSelect('category.children', 'product')
.leftJoinAndSelect('product.children', 'addon')
.getMany();
Please note how I used different names to reference them (category, product, and addon).
I believe I'm 3 years late, but better late than ever. The most upvoted answer does not answer the question, as it only works for tree-like and hierarchical structures, so if you follow that example, this would happen:
Fred
/ \
Albert Laura
/ \
John Foo
In this example, Foo can't be friends with Fred, because he can only have one parent. Friends is not a tree structure, it's like a net. The answer would be the following:
import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
#Entity(UserModel.MODEL_NAME)
export class UserModel {
static MODEL_NAME = 'users';
#PrimaryGeneratedColumn()
id?: number;
#Column({ type: 'varchar', unique: true, length: 50 })
username: string;
#Column({ type: 'varchar', length: 50, unique: true })
email: string;
#ManyToMany(type => UserModel)
#JoinTable()
friends: UserModel[];
#Column({ type: 'varchar', length: 300 })
password: string;
}
This would create a table where relations between people would be saved. Now for the next important stuff. How do you query this and get a user's friends? It's not as easy as it seems, I've played hours with this and haven't been able to do it with TypeORM methods or even query builder. The answer is: Raw Query. This would return an array with the user's friends:
async findFriends(id: Id): Promise<UserModel[]> {
return await this.userORM.query(
` SELECT *
FROM users U
WHERE U.id <> $1
AND EXISTS(
SELECT 1
FROM users_friends_users F
WHERE (F."usersId_1" = $1 AND F."usersId_2" = U.id )
OR (F."usersId_2" = $1 AND F."usersId_1" = U.id )
); `,
[id],
);
}
(users_friends_users is the autogenerated name that typeORM gives to the table where the relations between users are saved)
2021 here, was searching for the same problem and find a way to solve it without custom raw SQL (providing same model as example for simplicity):
import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn, JoinTable } from 'typeorm';
#Entity(UserModel.MODEL_NAME)
export class UserModel {
static MODEL_NAME = 'users';
#PrimaryGeneratedColumn()
id?: number;
#Column({ type: 'varchar', unique: true, length: 50 })
username: string;
#Column({ type: 'varchar', length: 50, unique: true })
email: string;
#ManyToMany(type => UserModel)
#JoinTable({ joinColumn: { name: 'users_id_1' } })
friends: UserModel[];
#Column({ type: 'varchar', length: 300 })
password: string;
}
The key moment here is to set joinColumn for JoinTable.
When you are defining ManyToMany relationship, TypeORM automatically creates n-n table users_friends_users with one column named user_id_1 and another user_id_2 (they are automatically incremented if foreign key is the same)
So it is enough to choose any column from this table as "primary join column" and it works
I'm working on a similar feature and facing the same problem.
Defined the same #ManyToMany relation as #Aleksandr Primak , but the relation isn't bi-directionnal.
Example:
Case 1 :
Auto-generated table users_friends_users contains value
[userId_1, userId_2] = 70, 19
When I'm logged with userId=70 and request currentUser, it returns the friends[19]
Case 2 :
Auto-generated table users_friends_users contains previously's value reversed
[userId_1, userId_2] = 19, 70
Still logged with userId=70 and request currentUser, it returns an empty list of friends[]
So I guess the only way is to use Raw Query as #Javi Marzán said