Skip to content
This repository has been archived by the owner on Nov 25, 2020. It is now read-only.

Commit

Permalink
feat(project): add identifier decorator to support the duplicated res…
Browse files Browse the repository at this point in the history
…ources
  • Loading branch information
mroseboom authored and RWOverdijk committed May 22, 2017
1 parent ea7346f commit 286453f
Show file tree
Hide file tree
Showing 13 changed files with 255 additions and 5 deletions.
14 changes: 14 additions & 0 deletions doc/decorators.md
Expand Up @@ -43,6 +43,20 @@ class HelloWorld {}
class HelloWorld {} class HelloWorld {}
``` ```


## @identifier()

This decorator comes in handy when you have the same resource name for multiple endpoints. Without it, aurelia-orm will use the resource decorator. The identifier will be used to register and retrieve the repository for the given entity.

When left empty, the name of the class (.toLowerCase()) will be used as the resource name. This is usually fine.

> **NOTE:** Leaving the decorator without a value is bad idea for bundling, because the bundler renames your modules it will not longer match. For more information see [bundling](https://github.com/SpoonX/aurelia-orm#bundling)
```js
// Sets the identifier to "i-want-bacon"
@identifier('i-want-bacon')
class HelloWorld {}
```

## @resource() ## @resource()


This decorator is probably the most important one. Without it, aurelia-orm won't know what your **custom entity** is all about. The resource maps to the API endpoint it represents. Simply put, resource `foo` maps to `/foo`. This decorator is probably the most important one. Without it, aurelia-orm won't know what your **custom entity** is all about. The resource maps to the API endpoint it represents. Simply put, resource `foo` maps to `/foo`.
Expand Down
16 changes: 16 additions & 0 deletions src/decorator/identifier.js
@@ -0,0 +1,16 @@
import {OrmMetadata} from '../orm-metadata';

/**
* Set the 'identifierName' metadata on the entity
*
* @param {string} identifierName The name of the identifier
*
* @return {function}
*
* @decorator
*/
export function identifier(identifierName) {
return function(target) {
OrmMetadata.forTarget(target).put('identifier', identifierName || target.name.toLowerCase());
};
}
22 changes: 17 additions & 5 deletions src/entity-manager.js
Expand Up @@ -47,7 +47,9 @@ export class EntityManager {
* @chainable * @chainable
*/ */
registerEntity(EntityClass) { registerEntity(EntityClass) {
this.entities[OrmMetadata.forTarget(EntityClass).fetch('resource')] = EntityClass; let meta = OrmMetadata.forTarget(EntityClass);

this.entities[meta.fetch('identifier') || meta.fetch('resource')] = EntityClass;


return this; return this;
} }
Expand All @@ -61,13 +63,18 @@ export class EntityManager {
* @throws {Error} * @throws {Error}
*/ */
getRepository(entity) { getRepository(entity) {
let reference = this.resolveEntityReference(entity); let reference = this.resolveEntityReference(entity);
let resource = entity; let identifier = entity;
let resource = entity;


if (typeof reference.getResource === 'function') { if (typeof reference.getResource === 'function') {
resource = reference.getResource() || resource; resource = reference.getResource() || resource;
} }


if (typeof reference.getIdentifier === 'function') {
identifier = reference.getIdentifier() || resource;
}

if (typeof resource !== 'string') { if (typeof resource !== 'string') {
throw new Error('Unable to find resource for entity.'); throw new Error('Unable to find resource for entity.');
} }
Expand All @@ -90,11 +97,12 @@ export class EntityManager {
// Tell the repository instance what resource it should use. // Tell the repository instance what resource it should use.
instance.setMeta(metaData); instance.setMeta(metaData);
instance.resource = resource; instance.resource = resource;
instance.identifier = identifier;
instance.entityManager = this; instance.entityManager = this;


if (instance instanceof DefaultRepository) { if (instance instanceof DefaultRepository) {
// This is a default repository. We'll cache this instance. // This is a default repository. We'll cache this instance.
this.repositories[resource] = instance; this.repositories[identifier] = instance;
} }


return instance; return instance;
Expand Down Expand Up @@ -133,13 +141,15 @@ export class EntityManager {
let reference = this.resolveEntityReference(entity); let reference = this.resolveEntityReference(entity);
let instance = this.container.get(reference); let instance = this.container.get(reference);
let resource = reference.getResource(); let resource = reference.getResource();
let identifier = reference.getIdentifier() || resource;


if (!resource) { if (!resource) {
if (typeof entity !== 'string') { if (typeof entity !== 'string') {
throw new Error('Unable to find resource for entity.'); throw new Error('Unable to find resource for entity.');
} }


resource = entity; resource = entity;
identifier = entity;
} }


// Set the validator. // Set the validator.
Expand All @@ -149,6 +159,8 @@ export class EntityManager {
instance.setValidator(validator); instance.setValidator(validator);
} }


return instance.setResource(resource).setRepository(this.getRepository(resource)); return instance.setResource(resource)
.setIdentifier(identifier)
.setRepository(this.getRepository(identifier));
} }
} }
31 changes: 31 additions & 0 deletions src/entity.js
Expand Up @@ -407,6 +407,37 @@ export class Entity {


return this; return this;
} }

/**
* Get the identifier name of this entity's reference (static).
*
* @return {string|null}
*/
static getIdentifier() {
return OrmMetadata.forTarget(this).fetch('identifier');
}

/**
* Get the identifier name of this entity instance
*
* @return {string|null}
*/
getIdentifier() {
return this.__identifier || this.getMeta().fetch('identifier');
}

/**
* Set this instance's identifier.
*
* @param {string} identifier
*
* @return {Entity} itself
* @chainable
*/
setIdentifier(identifier) {
return this.define('__identifier', identifier);
}

/** /**
* Get the resource name of this entity's reference (static). * Get the resource name of this entity's reference (static).
* *
Expand Down
1 change: 1 addition & 0 deletions src/orm-metadata.js
Expand Up @@ -21,6 +21,7 @@ export class Metadata {
constructor() { constructor() {
this.metadata = { this.metadata = {
repository : DefaultRepository, repository : DefaultRepository,
identifier : null,
resource : null, resource : null,
endpoint : null, endpoint : null,
name : null, name : null,
Expand Down
22 changes: 22 additions & 0 deletions src/repository.js
Expand Up @@ -54,6 +54,28 @@ export class Repository {
return this.meta; return this.meta;
} }


/**
* Set the identifier
*
* @param {string} identifier
* @return {Repository} this
* @chainable
*/
setIdentifier(identifier) {
this.identifier = identifier;

return this;
}

/**
* Get the identifier
*
* @return {string|null}
*/
getIdentifier() {
return this.identifier;
}

/** /**
* Set the resource * Set the resource
* *
Expand Down
40 changes: 40 additions & 0 deletions test/decorator/identifier.spec.js
@@ -0,0 +1,40 @@
import {WithIdentifier} from '../resources/entity/with-identifier';
import {FirstWithSameResource, SecondWithSameResource} from '../resources/entity/with-same-resource';
import {FirstWithSameResourceAndIdentifier, SecondWithSameResourceAndIdentifier} from '../resources/entity/with-same-resource-and-identifier';
import {OrmMetadata} from '../../src/orm-metadata';
import {Entity} from '../../src/entity';
import {EntityManager} from '../../src/entity-manager';
import {Container} from 'aurelia-dependency-injection';

describe('@identifier()', function () {
it('Should add identifier on the entity', function () {
expect(OrmMetadata.forTarget(WithIdentifier).fetch('identifier')).toEqual('with-identifier');
});

it('Should override the first entity when the same resource key is used without the identifier decorator', function () {
let entityManager = new EntityManager(new Container());

entityManager.registerEntities([FirstWithSameResource, SecondWithSameResource]);

expect(Object.keys(entityManager.entities).length).toBe(1);

expect(entityManager.entities).toEqual({ 'with-duplicated-resource': SecondWithSameResource });
expect(entityManager.entities).not.toEqual({ 'with-duplicated-resource': FirstWithSameResource });
});

it('Should register two entities when the identifier decorator is applied with both the same resource name', function () {
let entityManager = new EntityManager(new Container());

entityManager.registerEntities([FirstWithSameResourceAndIdentifier, SecondWithSameResourceAndIdentifier]);

expect(Object.keys(entityManager.entities).length).toBe(2);

expect(OrmMetadata.forTarget(FirstWithSameResourceAndIdentifier).fetch('identifier')).toEqual('with-identifier-foo');
expect(OrmMetadata.forTarget(FirstWithSameResourceAndIdentifier).fetch('resource')).toEqual('with-duplicated-resource');
expect(OrmMetadata.forTarget(FirstWithSameResourceAndIdentifier).fetch('endpoint')).toEqual('with-endpoint-foo');

expect(OrmMetadata.forTarget(SecondWithSameResourceAndIdentifier).fetch('identifier')).toEqual('with-identifier-bar');
expect(OrmMetadata.forTarget(SecondWithSameResourceAndIdentifier).fetch('resource')).toEqual('with-duplicated-resource');
expect(OrmMetadata.forTarget(SecondWithSameResourceAndIdentifier).fetch('endpoint')).toEqual('with-endpoint-bar');
});
});
47 changes: 47 additions & 0 deletions test/entity-manager.spec.js
@@ -1,6 +1,7 @@
import {EntityManager} from '../src/entity-manager'; import {EntityManager} from '../src/entity-manager';
import {Container} from 'aurelia-dependency-injection'; import {Container} from 'aurelia-dependency-injection';
import {WithResource} from './resources/entity/with-resource'; import {WithResource} from './resources/entity/with-resource';
import {WithIdentifier} from './resources/entity/with-identifier';
import {WithCustomRepository} from './resources/entity/with-custom-repository'; import {WithCustomRepository} from './resources/entity/with-custom-repository';
import {SimpleCustom} from './resources/repository/simple-custom'; import {SimpleCustom} from './resources/repository/simple-custom';
import {DefaultRepository} from '../src/default-repository'; import {DefaultRepository} from '../src/default-repository';
Expand All @@ -16,6 +17,14 @@ describe('EntityManager', function() {
expect(entityManager.entities).toEqual({'with-resource': WithResource}); expect(entityManager.entities).toEqual({'with-resource': WithResource});
}); });


it('Should register entities with the manager and identifier decorator', function() {
let entityManager = new EntityManager(new Container());

entityManager.registerEntities([WithIdentifier]);

expect(entityManager.entities).toEqual({'with-identifier': WithIdentifier});
});

it('Should return self.', function() { it('Should return self.', function() {
let entityManager = new EntityManager(new Container()); let entityManager = new EntityManager(new Container());


Expand All @@ -38,6 +47,14 @@ describe('EntityManager', function() {
expect(entityManager.entities).toEqual({'with-resource': WithResource}); expect(entityManager.entities).toEqual({'with-resource': WithResource});
}); });


it('Should register an entity with the manager and identifier decorator', function() {
let entityManager = new EntityManager(new Container());

entityManager.registerEntity(WithIdentifier);

expect(entityManager.entities).toEqual({'with-identifier': WithIdentifier});
});

xit('Should throw when register with non-Entity', function() { xit('Should throw when register with non-Entity', function() {
let entityManager = new EntityManager(new Container()); let entityManager = new EntityManager(new Container());
class Wrong {} class Wrong {}
Expand Down Expand Up @@ -72,6 +89,22 @@ describe('EntityManager', function() {
expect(entityManager.getRepository(WithResource) instanceof DefaultRepository).toBe(true); expect(entityManager.getRepository(WithResource) instanceof DefaultRepository).toBe(true);
}); });


it('Should return the default repository when no custom specified. (Entity resource)', function() {
let entityManager = new EntityManager(new Container());

entityManager.registerEntity(WithIdentifier);

expect(entityManager.getRepository('with-identifier') instanceof DefaultRepository).toBe(true);
});

it('Should return the default repository when no custom specified. (Entity reference)', function() {
let entityManager = new EntityManager(new Container());

entityManager.registerEntity(WithIdentifier);

expect(entityManager.getRepository(WithIdentifier) instanceof DefaultRepository).toBe(true);
});

it('Should return the custom repository when specified.', function() { it('Should return the custom repository when specified.', function() {
let entityManager = new EntityManager(new Container()); let entityManager = new EntityManager(new Container());


Expand Down Expand Up @@ -164,6 +197,20 @@ describe('EntityManager', function() {
expect(entityManager.getEntity('with-resource') instanceof WithResource).toBe(true); expect(entityManager.getEntity('with-resource') instanceof WithResource).toBe(true);
}); });


it('Should return a new `WithIdentifier` instance (Entity reference).', function() {
let entityManager = new EntityManager(new Container());

expect(entityManager.getEntity(WithIdentifier) instanceof WithIdentifier).toBe(true);
});

it('Should return a new `WithIdentifier` instance (Entity resource).', function() {
let entityManager = new EntityManager(new Container());

entityManager.registerEntity(WithIdentifier);

expect(entityManager.getEntity('with-identifier') instanceof WithIdentifier).toBe(true);
});

it('Should return a new `Entity` instance.', function() { it('Should return a new `Entity` instance.', function() {
let entityManager = new EntityManager(new Container()); let entityManager = new EntityManager(new Container());


Expand Down
2 changes: 2 additions & 0 deletions test/orm-metadata.spec.js
Expand Up @@ -9,6 +9,7 @@ describe('OrmMetadata', function() {
expect(meta instanceof Metadata).toBe(true); expect(meta instanceof Metadata).toBe(true);
expect(meta.metadata).toEqual({ expect(meta.metadata).toEqual({
repository: DefaultRepository, repository: DefaultRepository,
identifier: null,
resource: null, resource: null,
endpoint: null, endpoint: null,
name: null, name: null,
Expand All @@ -24,6 +25,7 @@ describe('OrmMetadata', function() {
expect(meta instanceof Metadata).toBe(true); expect(meta instanceof Metadata).toBe(true);
expect(meta.metadata).toEqual({ expect(meta.metadata).toEqual({
repository: DefaultRepository, repository: DefaultRepository,
identifier: null,
resource: null, resource: null,
name: null, name: null,
endpoint: null, endpoint: null,
Expand Down
16 changes: 16 additions & 0 deletions test/repository.spec.js
Expand Up @@ -76,6 +76,22 @@ describe('Repository', function() {
}); });
}); });


describe('.setIdentifier()', function() {
it('Should set the identifier.', function() {
let repository = new Repository(getApiConfig());

repository.setIdentifier('foo');

expect(repository.identifier).toBe('foo');
});

it('Should return self.', function() {
let repository = new Repository(getApiConfig());

expect(repository.setIdentifier('foo')).toBe(repository);
});
});

describe('.setResource()', function() { describe('.setResource()', function() {
it('Should set the resource.', function() { it('Should set the resource.', function() {
let repository = new Repository(getApiConfig()); let repository = new Repository(getApiConfig());
Expand Down
11 changes: 11 additions & 0 deletions test/resources/entity/with-identifier.js
@@ -0,0 +1,11 @@
import {identifier} from '../../../src/decorator/identifier';
import {resource} from '../../../src/decorator/resource';
import {idProperty} from '../../../src/decorator/idProperty';
import {Entity} from '../../../src/entity';

@identifier('with-identifier')
@resource('with-resource')
@idProperty('idTag')
export class WithIdentifier extends Entity {
foo = null;
}
20 changes: 20 additions & 0 deletions test/resources/entity/with-same-resource-and-identifier.js
@@ -0,0 +1,20 @@
import {endpoint} from '../../../src/decorator/endpoint';
import {identifier} from '../../../src/decorator/identifier';
import {resource} from '../../../src/decorator/resource';
import {Entity} from '../../../src/entity';

@identifier('with-identifier-foo')
@resource('with-duplicated-resource')
@endpoint('with-endpoint-foo')
export class FirstWithSameResourceAndIdentifier extends Entity {
foo = null;
}


@identifier('with-identifier-bar')
@resource('with-duplicated-resource')
@endpoint('with-endpoint-bar')
export class SecondWithSameResourceAndIdentifier extends Entity {
foo = null;
}

18 changes: 18 additions & 0 deletions test/resources/entity/with-same-resource.js
@@ -0,0 +1,18 @@
import {endpoint} from '../../../src/decorator/endpoint';
import {identifier} from '../../../src/decorator/identifier';
import {resource} from '../../../src/decorator/resource';
import {Entity} from '../../../src/entity';

@resource('with-duplicated-resource')
@endpoint('with-endpoint-foo')
export class FirstWithSameResource extends Entity {
foo = null;
}


@resource('with-duplicated-resource')
@endpoint('with-endpoint-bar')
export class SecondWithSameResource extends Entity {
foo = null;
}

0 comments on commit 286453f

Please sign in to comment.