Skip to content

Commit

Permalink
Use immutable object for model attribute persistance
Browse files Browse the repository at this point in the history
  • Loading branch information
hudson-newey committed Nov 19, 2024
1 parent 91a0141 commit afbdf69
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 26 deletions.
37 changes: 21 additions & 16 deletions src/app/models/AbstractModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ export type AbstractModelConstructor<Model> = new (
*/
export abstract class AbstractModelWithoutId<Model = Record<string, any>> {
public constructor(raw: Model, protected injector?: AssociationInjector) {
this.getPersistentAttributes()
const transformedRaw = this.getPersistentAttributes()
.filter((attr) => attr.convertCase)
.forEach((attr) => {
.reduce((acc, attr) => {
const value = raw[attr.key];
raw[attr.key] = isInstantiated(value) ? camelCase(value) : value;
});
acc[attr.key] = isInstantiated(value) ? camelCase(value) : value;
return acc;
}, {});

return Object.assign(this, raw);
Object.assign(this, transformedRaw, raw);
}

/** Keys for accessing hidden data associated with a model */
Expand Down Expand Up @@ -229,17 +230,21 @@ export abstract class AbstractModelWithoutId<Model = Record<string, any>> {
*/
private getModelAttributes(opts?: ModelSerializationOptions): string[] {
if (opts?.create || opts?.update) {
return this.getPersistentAttributes()
.filter((meta) => (opts.create ? meta.create : meta.update))
// The following filter splits values for attributes that support both json and formData formats
// when a null value is present, we send the value in the json request
// when a File value is present, we send the value in the formData request
// The null/json scenario is used to support deleting images.
.filter((meta) => this[meta.key] instanceof(File) ? opts.formData : true)
.filter((meta) =>
meta.supportedFormats.includes(opts.formData ? "formData" : "json")
)
.map((meta) => meta.key);
return (
this.getPersistentAttributes()
.filter((meta) => (opts.create ? meta.create : meta.update))
// The following filter splits values for attributes that support both json and formData formats
// when a null value is present, we send the value in the json request
// when a File value is present, we send the value in the formData request
// The null/json scenario is used to support deleting images.
.filter((meta) =>
this[meta.key] instanceof File ? opts.formData : true
)
.filter((meta) =>
meta.supportedFormats.includes(opts.formData ? "formData" : "json")
)
.map((meta) => meta.key)
);
} else {
return Object.keys(this).filter((key) => key !== "injector");
}
Expand Down
8 changes: 6 additions & 2 deletions src/app/services/baw-api/baw-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { ASSOCIATION_INJECTOR } from "@services/association-injector/association
import { AssociationInjector } from "@models/ImplementsInjector";
import {
NgHttpCachingConfig,
NgHttpCachingContext,
NgHttpCachingService,
withNgHttpCachingContext,
} from "ng-http-caching";
Expand Down Expand Up @@ -137,7 +138,7 @@ export class BawApiService<

private buildCachingOptions(
options: Partial<BawServiceOptions>
): NgHttpCachingConfig {
): NgHttpCachingContext {
return {
...this.instanceOptions.cacheOptions,
...options.cacheOptions,
Expand Down Expand Up @@ -458,7 +459,10 @@ export class BawApiService<
const fullOptions = this.buildServiceOptions(options);

const cachingOptions = this.buildCachingOptions(options);
const cacheContext = withNgHttpCachingContext(cachingOptions, withCacheLogging());
const cacheContext = withNgHttpCachingContext(
cachingOptions,
withCacheLogging()
);

const context = this.withCredentialsHttpContext(fullOptions, cacheContext);

Expand Down
14 changes: 11 additions & 3 deletions src/app/services/cache/ngHttpCachingConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,17 @@ export const defaultCachingConfig = {
// by the user. E.g. paging through a list of items
// see: https://github.com/QutEcoacoustics/workbench-client/pull/2171#issuecomment-2484676601
//
// we are trialing a 10 second cache lifetime in the hope that users doing any
// quick succession actions, they'll see a benefit from the cache
// e.g. flicking through a page of results
// I have temporarily set the cache lifetime to 0 seconds because the
// @bawReadonlyConvertCase decorator attempts to modify frozen cached objects
// when recalling items from the cache
// this causes an an error similar to "status is read-only"
// by setting the cache lifetime to 0 seconds, we can get the performance
// gains of request debouncing without having potential errors due to items
// being recalled from the cache
//
// in the future we should use a 10 second cache lifetime in the hope that
// users doing any quick succession actions, they'll see a benefit from the
// cache e.g. flicking through a page of results
// while users who perform actions on a page are likely to take longer than 10
// seconds, which will hopefully prevent stale caching issues
lifetime: secondsToMilliseconds(10),
Expand Down
8 changes: 3 additions & 5 deletions src/app/services/models/annotation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,9 @@ export class AnnotationService {
private async showAudioRecording(
audioEvent: AudioEvent
): Promise<AudioRecording> {
return new AudioRecording(
await firstValueFrom(
this.audioRecordingsApi.show(audioEvent.audioRecordingId)
)
);
return await firstValueFrom(
this.audioRecordingsApi.show(audioEvent.audioRecordingId)
)
}
}

Expand Down

0 comments on commit afbdf69

Please sign in to comment.