Skip to content

Commit

Permalink
remove support for non-context usage
Browse files Browse the repository at this point in the history
  • Loading branch information
mickhansen committed May 29, 2019
1 parent 8625bd0 commit 5f3c766
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 635 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"isparta": "^4.0.0",
"mocha": "^3.0.0",
"pg": "^6.1.0",
"sequelize": "^5.2.8",
"sequelize": "^5.8.7",
"sinon": "^1.17.4",
"sinon-as-promised": "^4.0.0",
"unexpected": "^10.14.2",
Expand Down
145 changes: 24 additions & 121 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,23 +105,6 @@ function rejectOnEmpty(options, result) {
return result;
}

function cachedLoaderForBTM(model, joinTableName, foreignKey, foreignKeyField, options = {}) {
assert(options.include === undefined, 'options.include is not supported by model loader');
assert(options.association !== undefined, 'options.association should be set for BTM loader');

let attributes = [joinTableName, foreignKey]
, cacheKey = getCacheKey(model, attributes, options);

if (!GLOBAL_CACHE.has(cacheKey)) {
GLOBAL_CACHE.set(cacheKey, loaderForBTM(model, joinTableName, foreignKey, foreignKeyField, {
...options,
cache: false
}));
}

return GLOBAL_CACHE.get(cacheKey);
}

function loaderForBTM(model, joinTableName, foreignKey, foreignKeyField, options = {}) {
assert(options.include === undefined, 'options.include is not supported by model loader');
assert(options.association !== undefined, 'options.association should be set for BTM loader');
Expand Down Expand Up @@ -158,21 +141,6 @@ function loaderForBTM(model, joinTableName, foreignKey, foreignKeyField, options
});
}

function cachedLoaderForModel(model, attribute, attributeField, options = {}) {
assert(options.include === undefined, 'options.include is not supported by model loader');

let cacheKey = getCacheKey(model, attribute, options);

if (!GLOBAL_CACHE.has(cacheKey)) {
GLOBAL_CACHE.set(cacheKey, loaderForModel(model, attribute, attributeField, {
...options,
cache: false
}));
}

return GLOBAL_CACHE.get(cacheKey);
}

function loaderForModel(model, attribute, attributeField, options = {}) {
assert(options.include === undefined, 'options.include is not supported by model loader');

Expand Down Expand Up @@ -209,16 +177,11 @@ function shimModel(target) {
if ([null, undefined].indexOf(id) !== -1) {
return Promise.resolve(null);
}
if (options.transaction || options.include || activeClsTransaction()) {
if (options.transaction || options.include || activeClsTransaction() || !options[EXPECTED_OPTIONS_KEY]) {
return original.apply(this, arguments);
}

let loader = null;
if (options[EXPECTED_OPTIONS_KEY]) {
loader = options[EXPECTED_OPTIONS_KEY].loaders[this.name].byPrimaryKey;
} else {
loader = cachedLoaderForModel(this, this.primaryKeyAttribute, this.primaryKeyField, options);
}
const loader = options[EXPECTED_OPTIONS_KEY].loaders[this.name].byPrimaryKey;
return Promise.resolve(loader.load(id)).then(rejectOnEmpty.bind(null, options));
};
});
Expand All @@ -229,7 +192,7 @@ function shimBelongsTo(target) {

shimmer.wrap(target, 'get', original => {
return function batchedGetBelongsTo(instance, options = {}) {
if (Array.isArray(instance) || options.include || options.transaction || activeClsTransaction()) {
if (Array.isArray(instance) || options.include || options.transaction || activeClsTransaction() || !options[EXPECTED_OPTIONS_KEY] || options.where) {
return original.apply(this, arguments);
}

Expand All @@ -239,12 +202,7 @@ function shimBelongsTo(target) {
return Promise.resolve(null);
}

let loader = null;
if (options[EXPECTED_OPTIONS_KEY] && !options.where) {
loader = options[EXPECTED_OPTIONS_KEY].loaders[this.target.name].bySingleAttribute[this.targetKey];
} else {
loader = cachedLoaderForModel(this.target, this.targetKey, this.targetKeyField, options);
}
const loader = options[EXPECTED_OPTIONS_KEY].loaders[this.target.name].bySingleAttribute[this.targetKey];
return Promise.resolve(loader.load(foreignKeyValue));
}).then(rejectOnEmpty.bind(null, options));
};
Expand All @@ -256,16 +214,11 @@ function shimHasOne(target) {

shimmer.wrap(target, 'get', original => {
return function batchedGetHasOne(instance, options = {}) {
if (Array.isArray(instance) || options.include || options.transaction || activeClsTransaction()) {
if (Array.isArray(instance) || options.include || options.transaction || activeClsTransaction() || !options[EXPECTED_OPTIONS_KEY]) {
return original.apply(this, arguments);
}

let loader = null;
if (options[EXPECTED_OPTIONS_KEY] && !options.where) {
loader = options[EXPECTED_OPTIONS_KEY].loaders[this.target.name].bySingleAttribute[this.foreignKey];
} else {
loader = cachedLoaderForModel(this.target, this.foreignKey, this.identifierField, options);
}
const loader = options[EXPECTED_OPTIONS_KEY].loaders[this.target.name].bySingleAttribute[this.foreignKey];
return Promise.resolve(loader.load(instance.get(this.sourceKey)).then(rejectOnEmpty.bind(null, options)));
};
});
Expand All @@ -277,7 +230,7 @@ function shimHasMany(target) {
shimmer.wrap(target, 'get', original => {
return function bathedGetHasMany(instances, options = {}) {
let isCount = false;
if (options.include || options.transaction || options.separate || activeClsTransaction()) {
if (options.include || options.transaction || options.separate || activeClsTransaction() || !options[EXPECTED_OPTIONS_KEY]) {
return original.apply(this, arguments);
}

Expand Down Expand Up @@ -307,18 +260,14 @@ function shimHasMany(target) {
...options
};

if (options[EXPECTED_OPTIONS_KEY]) {
const cacheKey = getCacheKey(this.target, this.foreignKey, loaderOptions);
loader = options[EXPECTED_OPTIONS_KEY].loaders.autogenerated.get(cacheKey);
if (!loader) {
loader = loaderForModel(this.target, this.foreignKey, this.foreignKeyField, {
...loaderOptions,
cache: true
});
options[EXPECTED_OPTIONS_KEY].loaders.autogenerated.set(cacheKey, loader);
}
} else {
loader = cachedLoaderForModel(this.target, this.foreignKey, this.foreignKeyField, loaderOptions);
const cacheKey = getCacheKey(this.target, this.foreignKey, loaderOptions);
loader = options[EXPECTED_OPTIONS_KEY].loaders.autogenerated.get(cacheKey);
if (!loader) {
loader = loaderForModel(this.target, this.foreignKey, this.foreignKeyField, {
...loaderOptions,
cache: true
});
options[EXPECTED_OPTIONS_KEY].loaders.autogenerated.set(cacheKey, loader);
}

let key = this.sourceKey || this.source.primaryKeyAttribute;
Expand Down Expand Up @@ -359,7 +308,7 @@ function shimBelongsToMany(target) {
let isCount = false;
assert(this.paired, '.paired missing on belongsToMany association. You need to set up both sides of the association');

if (options.include || options.transaction || activeClsTransaction()) {
if (options.include || options.transaction || activeClsTransaction() || !options[EXPECTED_OPTIONS_KEY]) {
return original.apply(this, arguments);
}

Expand Down Expand Up @@ -399,18 +348,14 @@ function shimBelongsToMany(target) {
...options
};

if (options[EXPECTED_OPTIONS_KEY]) {
const cacheKey = getCacheKey(this.target, [this.paired.manyFromSource.as, this.foreignKey], loaderOptions);
loader = options[EXPECTED_OPTIONS_KEY].loaders.autogenerated.get(cacheKey);
if (!loader) {
loader = loaderForBTM(this.target, this.paired.manyFromSource.as, this.foreignKey, this.identifierField, {
...loaderOptions,
cache: true
});
options[EXPECTED_OPTIONS_KEY].loaders.autogenerated.set(cacheKey, loader);
}
} else {
loader = cachedLoaderForBTM(this.target, this.paired.manyFromSource.as, this.foreignKey, this.identifierField, loaderOptions);
const cacheKey = getCacheKey(this.target, [this.paired.manyFromSource.as, this.foreignKey], loaderOptions);
loader = options[EXPECTED_OPTIONS_KEY].loaders.autogenerated.get(cacheKey);
if (!loader) {
loader = loaderForBTM(this.target, this.paired.manyFromSource.as, this.foreignKey, this.identifierField, {
...loaderOptions,
cache: true
});
options[EXPECTED_OPTIONS_KEY].loaders.autogenerated.set(cacheKey, loader);
}

if (Array.isArray(instances)) {
Expand All @@ -427,20 +372,6 @@ function shimBelongsToMany(target) {
});
}

function shimAssociation(association) {
switch (association.associationType) {
case 'BelongsTo': return shimBelongsTo(association);
case 'HasOne': return shimHasOne(association);
case 'HasMany': return shimHasMany(association);
case 'BelongsToMany': return shimBelongsToMany(association);
}
}

let GLOBAL_CACHE;
export function resetCache() {
if (GLOBAL_CACHE) GLOBAL_CACHE.reset();
}

function activeClsTransaction() {
if (/^[45]/.test(Sequelize.version)) {
if (Sequelize._cls && Sequelize._cls.get('transaction')) {
Expand All @@ -452,31 +383,6 @@ function activeClsTransaction() {
return false;
}

export default function (target, options = {}) {
options = {
...options,
max: 500
};

if (!GLOBAL_CACHE) {
GLOBAL_CACHE = LRU(options);
}

if (target.associationType) {
shimAssociation(target);
} else if (/(SequelizeModel|class extends Model)/.test(target.toString()) || Sequelize.Model.isPrototypeOf(target)) {
shimModel(target);
values(target.associations).forEach(shimAssociation);
} else {
// Assume target is the sequelize constructor
shimModel(/^[45]/.test(Sequelize.version) ? target.constructor.Model : target.constructor.Model.prototype);
shimBelongsTo(target.constructor.Association.BelongsTo.prototype);
shimHasOne(target.constructor.Association.HasOne.prototype);
shimHasMany(target.constructor.Association.HasMany.prototype);
shimBelongsToMany(target.constructor.Association.BelongsToMany.prototype);
}
}

export const EXPECTED_OPTIONS_KEY = 'dataloader_sequelize_context';
export function createContext(sequelize, options = {}) {
const loaders = {};
Expand All @@ -489,9 +395,6 @@ export function createContext(sequelize, options = {}) {
shimBelongsToMany(sequelize.constructor.Association.BelongsToMany.prototype);

loaders.autogenerated = LRU({max: options.max || 500});
if (!GLOBAL_CACHE) {
GLOBAL_CACHE = LRU(options);
}

sequelize.modelManager.forEachModel(Model => {
loaders[Model.name] = {
Expand Down
3 changes: 0 additions & 3 deletions test/helper.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import Sequelize from 'sequelize';
import {resetCache} from '../src';

beforeEach(resetCache);

export const connection = new Sequelize(
process.env.DB_DATABASE,
Expand Down
43 changes: 5 additions & 38 deletions test/integration/belongsTo.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Sequelize from 'sequelize';
import {createConnection, randint} from '../helper';
import sinon from 'sinon';
import dataloaderSequelize, {createContext, EXPECTED_OPTIONS_KEY} from '../../src';
import {createContext, EXPECTED_OPTIONS_KEY} from '../../src';
import Promise from 'bluebird';
import expect from 'unexpected';

Expand Down Expand Up @@ -29,8 +29,6 @@ describe('belongsTo', function () {
}
});

dataloaderSequelize(this.Project);

await this.connection.sync({
force: true
});
Expand All @@ -57,22 +55,7 @@ describe('belongsTo', function () {
this.context = createContext(this.connection);
});

it('batches to a single findAll call', async function () {
let user1 = this.project1.getOwner()
, user2 = this.project2.getOwner();

await expect(user1, 'to be fulfilled with', this.user1);
await expect(user2, 'to be fulfilled with', this.user2);

expect(this.User.findAll, 'was called once');
expect(this.User.findAll, 'to have a call satisfying', [{
where: {
id: [this.user1.get('id'), this.user2.get('id')]
}
}]);
});

it('batches and caches to a single findAll call (createContext)', async function () {
it('batches and caches to a single findAll call', async function () {
let user1 = this.project1.getOwner({[EXPECTED_OPTIONS_KEY]: this.context})
, user2 = this.project2.getOwner({[EXPECTED_OPTIONS_KEY]: this.context});

Expand Down Expand Up @@ -118,9 +101,9 @@ describe('belongsTo', function () {
});

it('supports rejectOnEmpty', async function () {
let user1 = this.project1.getOwner({ rejectOnEmpty: Error })
, user2 = this.project3.getOwner({ rejectOnEmpty: Error })
, user3 = this.project3.getOwner();
let user1 = this.project1.getOwner({ [EXPECTED_OPTIONS_KEY]: this.context, rejectOnEmpty: Error })
, user2 = this.project3.getOwner({ [EXPECTED_OPTIONS_KEY]: this.context, rejectOnEmpty: Error })
, user3 = this.project3.getOwner({ [EXPECTED_OPTIONS_KEY]: this.context });

await expect(user1, 'to be fulfilled with', this.user1);
await expect(user2, 'to be rejected with', Error);
Expand Down Expand Up @@ -170,27 +153,11 @@ describe('belongsTo', function () {
this.project2.setOwner(this.user2)
);

dataloaderSequelize(this.Project);
this.sandbox.spy(this.User, 'findAll');

this.context = createContext(this.connection);
});

it('batches to a single findAll call', async function () {
let user1 = this.project1.getOwner()
, user2 = this.project2.getOwner();

await expect(user1, 'to be fulfilled with', this.user1);
await expect(user2, 'to be fulfilled with', this.user2);

expect(this.User.findAll, 'was called once');
expect(this.User.findAll, 'to have a call satisfying', [{
where: {
some_id: [this.project1.get('ownerId'), this.project2.get('ownerId')]
}
}]);
});

it('batches and caches to a single findAll call (createContext)', async function () {
let user1 = this.project1.getOwner({[EXPECTED_OPTIONS_KEY]: this.context})
, user2 = this.project2.getOwner({[EXPECTED_OPTIONS_KEY]: this.context});
Expand Down
Loading

0 comments on commit 5f3c766

Please sign in to comment.