Skip to content
This repository was archived by the owner on Dec 29, 2020. It is now read-only.

Commit cb2aa64

Browse files
committed
lazy loading working
1 parent c420703 commit cb2aa64

File tree

4 files changed

+107
-58
lines changed

4 files changed

+107
-58
lines changed

server/src/entities/Party.ts

Lines changed: 40 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,24 @@ export class Party extends BaseEntity {
2828
@UpdateDateColumn()
2929
latestTime: Date
3030

31-
@ManyToOne(() => Song, { eager: true, nullable: true })
32-
currentSong: Song | null
31+
@ManyToOne(() => Song, { lazy: true, nullable: true })
32+
currentSong: Promise<Song | null>
3333

34-
@OneToMany(() => VotedSong, votedSong => votedSong.party, { eager: true, cascade: ['remove'] })
35-
votedSongs: VotedSong[]
34+
@Column({ nullable: true })
35+
currentSongId: number
3636

37-
@OneToMany(() => PlayedSong, playedSong => playedSong.party, { eager: true, cascade: ['remove'] })
38-
playedSongs: PlayedSong[]
37+
@OneToMany(() => VotedSong, votedSong => votedSong.party, { lazy: true, cascade: ['remove'] })
38+
votedSongs: Promise<VotedSong[]>
39+
40+
@OneToMany(() => PlayedSong, playedSong => playedSong.party, { lazy: true, cascade: ['remove'] })
41+
playedSongs: Promise<PlayedSong[]>
3942

4043
constructor(name: string, password?: string) {
4144
super()
4245

4346
this.name = name
4447
this.password = password || null
45-
this.currentSong = null
48+
this.currentSong = Promise.resolve(null)
4649
// Don't initialize this.playedSongs or this.votedSongs.
4750
// this.playedSongs = []
4851
// this.votedSongs = []
@@ -55,7 +58,7 @@ export class Party extends BaseEntity {
5558
}
5659

5760
public async voteForSong(song: Song) {
58-
let votedSong = await VotedSong.findOne({ song: song, party: this })
61+
let votedSong = await VotedSong.findOne({ where: { songId: song.id, partyId: this.id } })
5962

6063
if (votedSong) {
6164
await votedSong.incrementVote()
@@ -68,10 +71,14 @@ export class Party extends BaseEntity {
6871
}
6972

7073
private async removeCurrentSong() {
71-
if (this.currentSong) {
72-
const newPlayedSong = new PlayedSong(this.currentSong, this, await this.getNextSequenceNumber())
73-
this.currentSong = null
74-
this.playedSongs.push(newPlayedSong)
74+
const currentSong = await this.currentSong
75+
76+
if (currentSong) {
77+
const newPlayedSong = new PlayedSong(currentSong, this, await this.getNextSequenceNumber())
78+
this.currentSong = Promise.resolve(null)
79+
const playedSongs = await this.playedSongs
80+
playedSongs.push(newPlayedSong)
81+
this.playedSongs = Promise.resolve(playedSongs)
7582
await Promise.all([newPlayedSong.save(), this.save()])
7683
}
7784
}
@@ -80,50 +87,40 @@ export class Party extends BaseEntity {
8087
const highestVotedSong = await this.getHighestVotedSong()
8188

8289
if (highestVotedSong) {
83-
this.currentSong = highestVotedSong.song
90+
this.currentSongId = (await highestVotedSong.getSong()).id
8491
await Promise.all([highestVotedSong.remove(), this.save()])
8592
}
8693
}
8794

95+
public async getSortedVotedSongs() {
96+
const votedSongs = await this.votedSongs
97+
const songs = await Promise.all(votedSongs.map(votedSong => votedSong.getSong()))
98+
const zipped: [VotedSong, Song][] = votedSongs.map((votedSong, index) => {
99+
return [votedSong, songs[index]]
100+
})
101+
zipped.sort(([votedSong1, song1], [votedSong2, song2]) => {
102+
if (votedSong1.count != votedSong2.count) {
103+
return votedSong2.count - votedSong1.count
104+
}
105+
if (song1.title < song2.title) {
106+
return -1
107+
}
108+
return 0
109+
})
110+
return zipped.map(([votedSong, _song]) => votedSong)
111+
}
112+
88113
private async getHighestVotedSong() {
89114
// TODO: TypeORM can't sort by fields of eagerly joined rows. So we currently retrieve all VotedSong for the party
90115
// and then sort by song name and then sort by name among the VotedSong with the highest equivalent count and then
91116
// return the first VotedSong. Optimize this by using the TypeORM query builder to build an SQL query that can
92117
// JOIN and then order correctly to retrieve a single VotedSong rather than all VotedSong for the party.
93-
return VotedSong.find({ where: { party: this }, order: { count: 'DESC' } }).then(votedSongs => {
94-
if (!Array.isArray(votedSongs) || !votedSongs.length) {
95-
return null
96-
} else {
97-
const highestCount = votedSongs[0].count
98-
return votedSongs
99-
.filter(votedSong => votedSong.count == highestCount)
100-
.sort((votedSong1, votedSong2) => {
101-
if (votedSong1.count != votedSong2.count) {
102-
return votedSong2.count - votedSong1.count
103-
}
104-
if (votedSong1.song.title < votedSong2.song.title) {
105-
return -1
106-
}
107-
return 0
108-
})[0]
109-
}
110-
})
118+
const sortedVotedSongs = await this.getSortedVotedSongs()
119+
return sortedVotedSongs[0]
111120
}
112121

113122
private async getNextSequenceNumber() {
114123
const latestSong = await PlayedSong.findOne({ where: { party: this }, order: { sequenceNumber: 'DESC' } })
115124
return latestSong ? latestSong.sequenceNumber + 1 : 0
116125
}
117-
118-
public sortVotedSongs() {
119-
this.votedSongs.sort((votedSong1, votedSong2) => {
120-
if (votedSong1.count != votedSong2.count) {
121-
return votedSong2.count - votedSong1.count
122-
}
123-
if (votedSong1.song.title < votedSong2.song.title) {
124-
return -1
125-
}
126-
return 0
127-
})
128-
}
129126
}

server/src/entities/PlayedSong.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,33 @@ export class PlayedSong extends BaseEntity {
1111
@Column()
1212
sequenceNumber: number
1313

14-
@ManyToOne(() => Song, { eager: true, nullable: false })
15-
song: Song
14+
@ManyToOne(() => Song, { lazy: true, nullable: false })
15+
song: Promise<Song>
1616

17-
@ManyToOne(() => Party, party => party.playedSongs, { nullable: false })
18-
party: Party
17+
@Column()
18+
songId: number
19+
20+
@ManyToOne(() => Party, party => party.playedSongs, { nullable: false, lazy: true })
21+
party: Promise<Party>
22+
23+
@Column()
24+
partyId: number
1925

2026
constructor(song: Song, party: Party, sequenceNumber: number) {
2127
super()
2228

23-
this.song = song
24-
this.party = party
29+
this.song = Promise.resolve(song)
30+
this.party = Promise.resolve(party)
2531
this.sequenceNumber = sequenceNumber
2632
}
33+
34+
// For some reason the Song or Party fields return Promise<undefined> (I think a TypeORM bug), so we need the following two methods.
35+
// Piazza question: https://piazza.com/class/kfpm567u1e24eb?cid=123.
36+
public async getParty() {
37+
return Party.findOneOrFail(this.partyId)
38+
}
39+
40+
public async getSong() {
41+
return Song.findOneOrFail(this.songId)
42+
}
2743
}

server/src/entities/VotedSong.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,37 @@ export class VotedSong extends BaseEntity {
1111
@Column()
1212
count: number
1313

14-
@ManyToOne(() => Song, { eager: true })
15-
song: Song
14+
@ManyToOne(() => Song, { lazy: true, nullable: false })
15+
song: Promise<Song>
1616

17-
@ManyToOne(() => Party, party => party.votedSongs)
18-
party: Party
17+
@Column()
18+
songId: number
19+
20+
@ManyToOne(() => Party, party => party.votedSongs, { nullable: false, lazy: true })
21+
party: Promise<Party>
22+
23+
@Column()
24+
partyId: number
1925

2026
constructor(song: Song, party: Party) {
2127
super()
22-
this.song = song
23-
this.party = party
28+
this.song = Promise.resolve(song)
29+
this.party = Promise.resolve(party)
2430
this.count = 1
2531
}
2632

2733
public async incrementVote() {
2834
++this.count
2935
await this.save()
3036
}
37+
38+
// For some reason the Song or Party fields return Promise<undefined> (I think a TypeORM bug), so we need the following two methods.
39+
// Piazza question: https://piazza.com/class/kfpm567u1e24eb?cid=123.
40+
public async getParty() {
41+
return Party.findOneOrFail(this.partyId)
42+
}
43+
44+
public async getSong() {
45+
return Song.findOneOrFail(this.songId)
46+
}
3147
}

server/src/graphql/api.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { readFileSync } from 'fs'
22
import { PubSub } from 'graphql-yoga'
33
import path from 'path'
44
import { Party } from '../entities/Party'
5+
import { PlayedSong } from '../entities/PlayedSong'
56
import { Song } from '../entities/Song'
67
import { Resolvers } from './schema.types'
78

@@ -21,7 +22,6 @@ export const graphqlRoot: Resolvers<Context> = {
2122
Query: {
2223
party: async (_, { partyName, partyPassword }) => {
2324
const party = await Party.findOne({ name: partyName, password: partyPassword || null })
24-
party?.sortVotedSongs()
2525
return party || null
2626
},
2727
songs: () => Song.find(),
@@ -40,19 +40,39 @@ export const graphqlRoot: Resolvers<Context> = {
4040
createParty: async (_, { partyName, partyPassword }) => {
4141
const party = await new Party(partyName, partyPassword || undefined).save()
4242
await party.reload() // We have to reload() because save() doesn't return the entire Party object.
43-
party.sortVotedSongs()
4443
return party
4544
},
4645
nextSong: async (_, { partyId }) => {
4746
const party = await Party.findOne(partyId)
4847
await party?.playNextSong()
4948
await party?.reload()
50-
party?.sortVotedSongs()
5149
return party || null
5250
},
5351
},
5452
// Rely on the resolver chain and async/partial resolution to perform the data conversion necessary for the API.
5553
Party: {
5654
latestTime: parent => parent.latestTime.toString(),
55+
votedSongs: async self => {
56+
return self.getSortedVotedSongs()
57+
},
58+
playedSongs: async self => {
59+
// I cast here because playedSongs.song is a Promise, but I know that we'll just use the PlayedSong resolver to get an actual Song.
60+
return (await self.playedSongs) as any
61+
},
62+
currentSong: async self => {
63+
return await self.currentSong
64+
},
65+
},
66+
VotedSong: {
67+
song: async self => {
68+
return self.getSong()
69+
},
70+
},
71+
PlayedSong: {
72+
song: async self => {
73+
// For some reason the type of self is not PlayedSong so I have to cast it. TODO: investigate this.
74+
const parentPlayedSong = (self as unknown) as PlayedSong
75+
return parentPlayedSong.getSong()
76+
},
5777
},
5878
}

0 commit comments

Comments
 (0)