Skip to content

Commit

Permalink
fix(@aws-amplify/datastore): fix hasOne delete (#8191)
Browse files Browse the repository at this point in the history
  • Loading branch information
iartemiev authored May 3, 2021
1 parent 918797e commit d16a8fb
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 7 deletions.
48 changes: 47 additions & 1 deletion packages/datastore/__tests__/AsyncStorageAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
initSchema as initSchemaType,
} from '../src/datastore/datastore';
import { PersistentModelConstructor, SortDirection } from '../src/types';
import { Model, testSchema } from './helpers';
import { Model, User, Profile, testSchema } from './helpers';
import { Predicates } from '../src/predicates';

let initSchema: typeof initSchemaType;
Expand Down Expand Up @@ -123,4 +123,50 @@ describe('AsyncStorageAdapter tests', () => {
expect(spyOnMemory).not.toHaveBeenCalled();
});
});
describe('Delete', () => {
let User: PersistentModelConstructor<User>;
let Profile: PersistentModelConstructor<Profile>;
let profile1Id: string;
let user1Id: string;

beforeAll(async () => {
({ initSchema, DataStore } = require('../src/datastore/datastore'));

const classes = initSchema(testSchema());

({ User } = classes as {
User: PersistentModelConstructor<User>;
});

({ Profile } = classes as {
Profile: PersistentModelConstructor<Profile>;
});

({ id: profile1Id } = await DataStore.save(
new Profile({ firstName: 'Rick', lastName: 'Bob' })
));

({ id: user1Id } = await DataStore.save(
new User({ name: 'test', profileID: profile1Id })
));
});

it('Should perform a cascading delete on a record with a Has One relationship', async () => {
let user = await DataStore.query(User, user1Id);
let profile = await DataStore.query(Profile, profile1Id);

// double-checking that both of the records exist at first
expect(user.id).toEqual(user1Id);
expect(profile.id).toEqual(profile1Id);

await DataStore.delete(User, user1Id);

user = await DataStore.query(User, user1Id);
profile = await DataStore.query(Profile, profile1Id);

// both should be undefined, even though we only explicitly deleted the user
expect(user).toBeUndefined;
expect(profile).toBeUndefined;
});
});
});
49 changes: 48 additions & 1 deletion packages/datastore/__tests__/IndexedDBAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
initSchema as initSchemaType,
} from '../src/datastore/datastore';
import { PersistentModelConstructor, SortDirection } from '../src/types';
import { Model, testSchema } from './helpers';
import { Model, User, Profile, testSchema } from './helpers';
import { Predicates } from '../src/predicates';

let initSchema: typeof initSchemaType;
Expand Down Expand Up @@ -108,4 +108,51 @@ describe('IndexedDBAdapter tests', () => {
expect(spyOnMemory).not.toHaveBeenCalled();
});
});

describe('Delete', () => {
let User: PersistentModelConstructor<User>;
let Profile: PersistentModelConstructor<Profile>;
let profile1Id: string;
let user1Id: string;

beforeAll(async () => {
({ initSchema, DataStore } = require('../src/datastore/datastore'));

const classes = initSchema(testSchema());

({ User } = classes as {
User: PersistentModelConstructor<User>;
});

({ Profile } = classes as {
Profile: PersistentModelConstructor<Profile>;
});

({ id: profile1Id } = await DataStore.save(
new Profile({ firstName: 'Rick', lastName: 'Bob' })
));

({ id: user1Id } = await DataStore.save(
new User({ name: 'test', profileID: profile1Id })
));
});

it('Should perform a cascading delete on a record with a Has One relationship', async () => {
let user = await DataStore.query(User, user1Id);
let profile = await DataStore.query(Profile, profile1Id);

// double-checking that both of the records exist at first
expect(user.id).toEqual(user1Id);
expect(profile.id).toEqual(profile1Id);

await DataStore.delete(User, user1Id);

user = await DataStore.query(User, user1Id);
profile = await DataStore.query(Profile, profile1Id);

// both should be undefined, even though we only explicitly deleted the user
expect(user).toBeUndefined;
expect(profile).toBeUndefined;
});
});
});
93 changes: 93 additions & 0 deletions packages/datastore/__tests__/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ export declare class Comment {
public readonly post: Post;
}

export declare class User {
public readonly id: string;
public readonly name: string;
public readonly profileID: string;
}
export declare class Profile {
public readonly id: string;
public readonly firstName: string;
public readonly lastName: string;
}

export function testSchema(): Schema {
return {
enums: {},
Expand Down Expand Up @@ -205,6 +216,88 @@ export function testSchema(): Schema {
},
},
},
User: {
name: 'User',
fields: {
id: {
name: 'id',
isArray: false,
type: 'ID',
isRequired: true,
attributes: [],
},
name: {
name: 'name',
isArray: false,
type: 'String',
isRequired: false,
attributes: [],
},
profileID: {
name: 'profileID',
isArray: false,
type: 'ID',
isRequired: true,
attributes: [],
},
profile: {
name: 'profile',
isArray: false,
type: {
model: 'Profile',
},
isRequired: false,
attributes: [],
association: {
connectionType: 'HAS_ONE',
associatedWith: 'id',
targetName: 'profileID',
},
},
},
syncable: true,
pluralName: 'Users',
attributes: [
{
type: 'model',
properties: {},
},
],
},
Profile: {
name: 'Profile',
fields: {
id: {
name: 'id',
isArray: false,
type: 'ID',
isRequired: true,
attributes: [],
},
firstName: {
name: 'firstName',
isArray: false,
type: 'String',
isRequired: true,
attributes: [],
},
lastName: {
name: 'lastName',
isArray: false,
type: 'String',
isRequired: true,
attributes: [],
},
},
syncable: true,
pluralName: 'Profiles',
attributes: [
{
type: 'model',
properties: {},
},
],
},
},
nonModels: {
Metadata: {
Expand Down
9 changes: 7 additions & 2 deletions packages/datastore/src/storage/adapter/AsyncStorageAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ export class AsyncStorageAdapter implements Adapter {
deleteQueue: { storeName: string; items: T[] }[]
): Promise<void> {
for await (const rel of relations) {
const { relationType, modelName } = rel;
const { relationType, modelName, targetName } = rel;
const storeName = this.getStorename(nameSpace, modelName);

const index: string =
Expand All @@ -506,9 +506,14 @@ export class AsyncStorageAdapter implements Adapter {
switch (relationType) {
case 'HAS_ONE':
for await (const model of models) {
const hasOneIndex = index || 'byId';

const hasOneCustomField = targetName in model;
const value = hasOneCustomField ? model[targetName] : model.id;

const allRecords = await this.db.getAll(storeName);
const recordToDelete = allRecords.filter(
childItem => childItem[index] === model.id
childItem => childItem[hasOneIndex] === value
);

await this.deleteTraverse(
Expand Down
11 changes: 8 additions & 3 deletions packages/datastore/src/storage/adapter/IndexedDBAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,7 @@ class IndexedDBAdapter implements Adapter {
deleteQueue: { storeName: string; items: T[] }[]
): Promise<void> {
for await (const rel of relations) {
const { relationType, fieldName, modelName } = rel;
const { relationType, fieldName, modelName, targetName } = rel;
const storeName = this.getStorename(nameSpace, modelName);

const index: string =
Expand All @@ -705,11 +705,16 @@ class IndexedDBAdapter implements Adapter {
switch (relationType) {
case 'HAS_ONE':
for await (const model of models) {
const hasOneIndex = index || 'byId';

const hasOneCustomField = targetName in model;
const value = hasOneCustomField ? model[targetName] : model.id;

const recordToDelete = <T>await this.db
.transaction(storeName, 'readwrite')
.objectStore(storeName)
.index(index)
.get(model.id);
.index(hasOneIndex)
.get(value);

await this.deleteTraverse(
this.schema.namespaces[nameSpace].relationships[modelName]
Expand Down
1 change: 1 addition & 0 deletions packages/datastore/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export type ModelAssociation = AssociatedWith | TargetNameAssociation;
type AssociatedWith = {
connectionType: 'HAS_MANY' | 'HAS_ONE';
associatedWith: string;
targetName?: string;
};
export function isAssociatedWith(obj: any): obj is AssociatedWith {
return obj && obj.associatedWith;
Expand Down

0 comments on commit d16a8fb

Please sign in to comment.