Skip to content

Commit

Permalink
feat!: make generated association names lowerCamelCase by default (#1…
Browse files Browse the repository at this point in the history
…6514)

Co-authored-by: Rik Smale <13023439+WikiRik@users.noreply.github.com>
  • Loading branch information
ephys and WikiRik committed Jan 31, 2024
1 parent 1a9418e commit 6800fbd
Show file tree
Hide file tree
Showing 45 changed files with 1,082 additions and 1,053 deletions.
7 changes: 5 additions & 2 deletions packages/core/src/associations/helpers.ts
Expand Up @@ -290,8 +290,11 @@ export function normalizeBaseAssociationOptions<T extends AssociationOptions<any
};
}
} else {
as = isMultiAssociation ? target.options.name.plural : target.options.name.singular;
name = target.options.name;
as = lowerFirst(isMultiAssociation ? target.options.name.plural : target.options.name.singular);
name = {
plural: lowerFirst(target.options.name.plural),
singular: lowerFirst(target.options.name.singular),
};
}

return removeUndefined({
Expand Down
16 changes: 0 additions & 16 deletions packages/core/src/utils/object.ts
Expand Up @@ -11,7 +11,6 @@ import mergeWith from 'lodash/mergeWith';
import omitBy from 'lodash/omitBy.js';
import type { MapView } from './immutability.js';
import { combinedIterator, map } from './iterators.js';
import { camelize } from './string';
import { getComplexKeys } from './where.js';

export const EMPTY_OBJECT = Object.freeze(Object.create(null));
Expand Down Expand Up @@ -201,21 +200,6 @@ export function defaults(
return objectIn;
}

/**
* @param obj
* @returns A new object with camel-cased keys
* @private
*/
export function camelizeObjectKeys(obj: { [key: string]: any }) {
const newObj: { [key: string]: any } = Object.create(null);

for (const key of Object.keys(obj)) {
newObj[camelize(key)] = obj[key];
}

return newObj;
}

type NoUndefinedField<T> = { [P in keyof T]: Exclude<T[P], null | undefined> };

export function removeUndefined<T extends {}>(val: T): NoUndefinedField<T> {
Expand Down
13 changes: 2 additions & 11 deletions packages/core/src/utils/string.ts
@@ -1,5 +1,6 @@
import NodeUtil from 'node:util';
import * as _inflection from 'inflection';
import lowerFirst from 'lodash/lowerFirst';
import type { IndexOptions, TableName } from '../dialects/abstract/query-interface.js';
import { BaseSqlExpression } from '../expression-builders/base-sql-expression.js';

Expand All @@ -14,18 +15,8 @@ export function useInflection(newInflection: Inflection) {

/* String utils */

export function camelizeIf(str: string, condition: boolean): string {
let result = str;

if (condition) {
result = camelize(str);
}

return result;
}

export function camelize(str: string): string {
return str.trim().replaceAll(/[-_\s]+(.)?/g, (match, c) => c.toUpperCase());
return lowerFirst(str.trim()).replaceAll(/[-_\s]+(.)?/g, (match, c) => c.toUpperCase());
}

export function underscoredIf(str: string, condition: boolean): string {
Expand Down
486 changes: 215 additions & 271 deletions packages/core/test/integration/associations/belongs-to-many.test.js

Large diffs are not rendered by default.

24 changes: 12 additions & 12 deletions packages/core/test/integration/associations/belongs-to.test.js
Expand Up @@ -23,7 +23,7 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => {

expect(
Object.keys(Group.associations),
).to.deep.equal(['User', 'primaryUsers', 'secondaryUsers']);
).to.deep.equal(['user', 'primaryUsers', 'secondaryUsers']);
});
});

Expand Down Expand Up @@ -228,10 +228,10 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => {
Task.belongsTo(User);

await this.sequelize.sync({ force: true });
await expect(Task.create({ title: 'task', UserXYZId: 5 })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError);
await expect(Task.create({ title: 'task', userXYZId: 5 })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError);
const task = await Task.create({ title: 'task' });

await expect(Task.update({ title: 'taskUpdate', UserXYZId: 5 }, { where: { id: task.id } })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError);
await expect(Task.update({ title: 'taskUpdate', userXYZId: 5 }, { where: { id: task.id } })).to.be.rejectedWith(Sequelize.ForeignKeyConstraintError);
});

it('supports passing the primary key instead of an object', async function () {
Expand Down Expand Up @@ -372,8 +372,8 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => {

User.belongsTo(Account);

expect(User.getAttributes().AccountId).to.exist;
expect(User.getAttributes().AccountId.field).to.equal('account_id');
expect(User.getAttributes().accountId).to.exist;
expect(User.getAttributes().accountId.field).to.equal('account_id');
});

it('should use model name when using camelcase', function () {
Expand All @@ -382,8 +382,8 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => {

User.belongsTo(Account);

expect(User.getAttributes().AccountId).to.exist;
expect(User.getAttributes().AccountId.field).to.equal('AccountId');
expect(User.getAttributes().accountId).to.exist;
expect(User.getAttributes().accountId.field).to.equal('accountId');
});

it('should support specifying the field of a foreign key', async function () {
Expand Down Expand Up @@ -419,7 +419,7 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => {
});

// the sql query should correctly look at account_id instead of AccountId
expect(user.Account).to.exist;
expect(user.account).to.exist;
});

it('should set foreignKey on foreign table', async function () {
Expand Down Expand Up @@ -537,7 +537,7 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => {
await task.setUser(user);
await user.destroy();
await task.reload();
expect(task.UserId).to.equal(null);
expect(task.userId).to.equal(null);
});

it('should be possible to disable them', async function () {
Expand All @@ -552,7 +552,7 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => {
await task.setUser(user);
await user.destroy();
await task.reload();
expect(task.UserId).to.equal(user.id);
expect(task.userId).to.equal(user.id);
});

it('can cascade deletes', async function () {
Expand Down Expand Up @@ -635,7 +635,7 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => {
await user.sequelize.queryInterface.update(user, tableName, { id: 999 }, { id: user.id });
const tasks = await Task.findAll();
expect(tasks).to.have.length(1);
expect(tasks[0].UserId).to.equal(999);
expect(tasks[0].userId).to.equal(999);
});
}
});
Expand All @@ -658,7 +658,7 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => {
User.belongsTo(Group);

await this.sequelize.sync({ force: true });
expect(User.getAttributes().GroupPKBTName.type).to.an.instanceof(DataTypes.STRING);
expect(User.getAttributes().groupPKBTName.type).to.an.instanceof(DataTypes.STRING);
});

it('should support a non-primary key as the association column on a target without a primary key', async function () {
Expand Down
47 changes: 24 additions & 23 deletions packages/core/test/integration/associations/has-many.test.js
Expand Up @@ -25,7 +25,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => {
Group.hasMany(User, { foreignKey: 'primaryGroupId', as: 'primaryUsers', inverse: { as: 'primaryGroup' } });
Group.hasMany(User, { foreignKey: 'secondaryGroupId', as: 'secondaryUsers', inverse: { as: 'secondaryGroup' } });

expect(Object.keys(Group.associations)).to.deep.equal(['Users', 'primaryUsers', 'secondaryUsers']);
expect(Object.keys(Group.associations)).to.deep.equal(['users', 'primaryUsers', 'secondaryUsers']);
});
});

Expand All @@ -41,16 +41,16 @@ describe(Support.getTestDialectTeaser('HasMany'), () => {

const user0 = await User.create({
username: 'John',
Tasks: [{
tasks: [{
title: 'Get rich', active: true,
}],
}, {
include: [Task],
});

await Promise.all([
user0.get('Tasks')[0].createSubtask({ title: 'Make a startup', active: false }),
user0.get('Tasks')[0].createSubtask({ title: 'Engage rock stars', active: true }),
user0.get('tasks')[0].createSubtask({ title: 'Make a startup', active: false }),
user0.get('tasks')[0].createSubtask({ title: 'Engage rock stars', active: true }),
]);

const user = user0;
Expand Down Expand Up @@ -497,8 +497,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => {
this.Label.belongsTo(this.Article);
this.Article.hasMany(this.Label);

expect(Object.keys(this.Label.getAttributes())).to.deep.equal(['id', 'text', 'ArticleId']);
expect(Object.keys(this.Label.getAttributes()).length).to.equal(3);
expect(Object.keys(this.Label.getAttributes())).to.deep.equal(['id', 'text', 'articleId']);
});

if (current.dialect.supports.transactions) {
Expand Down Expand Up @@ -713,10 +712,10 @@ describe(Support.getTestDialectTeaser('HasMany'), () => {

const t = await sequelize.startUnmanagedTransaction();
await article.addLabel(label, { transaction: t });
const labels0 = await Label.findAll({ where: { ArticleId: article.id }, transaction: undefined });
const labels0 = await Label.findAll({ where: { articleId: article.id }, transaction: undefined });
expect(labels0.length).to.equal(0);

const labels = await Label.findAll({ where: { ArticleId: article.id }, transaction: t });
const labels = await Label.findAll({ where: { articleId: article.id }, transaction: t });
expect(labels.length).to.equal(1);
await t.rollback();
});
Expand Down Expand Up @@ -811,7 +810,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => {
const article0 = await Article.create({ title: 'foo' });
await article0.createLabel({ text: 'bar' });
const article = article0;
const labels = await Label.findAll({ where: { ArticleId: article.id } });
const labels = await Label.findAll({ where: { articleId: article.id } });
expect(labels.length).to.equal(1);
});

Expand All @@ -832,7 +831,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => {
const article = await Article.create({ title: 'foo' });
const label = await article.createLabel({ text: 'bar' }, { logging: spy });
expect(spy.calledOnce).to.be.true;
expect(label.ArticleId).to.equal(article.id);
expect(label.articleId).to.equal(article.id);
});

if (current.dialect.supports.transactions) {
Expand All @@ -849,9 +848,9 @@ describe(Support.getTestDialectTeaser('HasMany'), () => {
await article.createLabel({ text: 'bar' }, { transaction: t });
const labels1 = await Label.findAll();
expect(labels1.length).to.equal(0);
const labels0 = await Label.findAll({ where: { ArticleId: article.id } });
const labels0 = await Label.findAll({ where: { articleId: article.id } });
expect(labels0.length).to.equal(0);
const labels = await Label.findAll({ where: { ArticleId: article.id }, transaction: t });
const labels = await Label.findAll({ where: { articleId: article.id }, transaction: t });
expect(labels.length).to.equal(1);
await t.rollback();
});
Expand Down Expand Up @@ -1018,7 +1017,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => {
await user.setTasks([task0]);
await user.destroy();
const task = await task0.reload();
expect(task.UserId).to.equal(null);
expect(task.userId).to.equal(null);
});

it('sets to CASCADE if allowNull: false', async function () {
Expand All @@ -1030,7 +1029,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => {
await this.sequelize.sync({ force: true });

const user = await User.create({ username: 'foo' });
await Task.create({ title: 'task', UserId: user.id });
await Task.create({ title: 'task', userId: user.id });
await user.destroy();
const tasks = await Task.findAll();
expect(tasks).to.be.empty;
Expand All @@ -1053,7 +1052,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => {
await user.setTasks([task0]);
await user.destroy();
await task.reload();
expect(task.UserId).to.equal(user.id);
expect(task.userId).to.equal(user.id);
});

it('can cascade deletes', async function () {
Expand Down Expand Up @@ -1100,7 +1099,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => {
await user.sequelize.queryInterface.update(user, tableName, { id: 999 }, { id: user.id });
const tasks = await Task.findAll();
expect(tasks).to.have.length(1);
expect(tasks[0].UserId).to.equal(999);
expect(tasks[0].userId).to.equal(999);
});
}

Expand Down Expand Up @@ -1181,8 +1180,8 @@ describe(Support.getTestDialectTeaser('HasMany'), () => {

User.hasMany(Account);

expect(Account.getAttributes().UserId).to.exist;
expect(Account.getAttributes().UserId.field).to.equal('user_id');
expect(Account.getAttributes().userId).to.exist;
expect(Account.getAttributes().userId.field).to.equal('user_id');
});

it('should use model name when using camelcase', function () {
Expand All @@ -1191,8 +1190,8 @@ describe(Support.getTestDialectTeaser('HasMany'), () => {

User.hasMany(Account);

expect(Account.getAttributes().UserId).to.exist;
expect(Account.getAttributes().UserId.field).to.equal('UserId');
expect(Account.getAttributes().userId).to.exist;
expect(Account.getAttributes().userId.field).to.equal('userId');
});

it('can specify data type for auto-generated relational keys', async function () {
Expand Down Expand Up @@ -1223,7 +1222,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => {
User.hasMany(Task);

await this.sequelize.sync({ force: true });
expect(Task.getAttributes().UserId.type instanceof DataTypes.STRING).to.be.ok;
expect(Task.getAttributes().userId.type instanceof DataTypes.STRING).to.be.ok;
});

describe('allows the user to provide an attribute definition object as foreignKey', () => {
Expand Down Expand Up @@ -1448,7 +1447,9 @@ describe(Support.getTestDialectTeaser('HasMany'), () => {
},
},
});

Child.belongsTo(Parent, {
as: 'Parent',
foreignKey: 'parent',
targetKey: 'id',
inverse: {
Expand Down Expand Up @@ -1511,8 +1512,8 @@ describe(Support.getTestDialectTeaser('HasMany'), () => {
});

expect(user).to.be.ok;
expect(user.Tasks.length).to.equal(1);
expect(user.Tasks[0].title).to.equal('Active Task');
expect(user.tasks.length).to.equal(1);
expect(user.tasks[0].title).to.equal('Active Task');
});
});

Expand Down

0 comments on commit 6800fbd

Please sign in to comment.