Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: await PK on create before updating relationships #740

17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Lux Changelog

### v1.2.3 (July 20, 2018)

* [[`56225dac71`](https://github.com/postlight/lux/commit/56225dac71)] - **fix**: falsy IDs breaking relationships (#730) (Nick Schot)
* [[`667febd98f`](https://github.com/postlight/lux/commit/667febd98f)] - **release**: v1.2.2 🔧 (#722) (Zachary Golba)

### v1.2.2 (Aug 28, 2017)

* [[`6c22bdf071`](https://github.com/postlight/lux/commit/6c22bdf071)] - **fix**: do not validate param existence for patch requests (#721) (Zachary Golba)
* [[`ea2b8f9926`](https://github.com/postlight/lux/commit/ea2b8f9926)] - **release**: v1.2.1 🔧 (#716) (Zachary Golba)

### v1.2.1 (July 11, 2017)

* [[`6b3547f436`](https://github.com/postlight/lux/commit/6b3547f436)] - **fix**: filtering with an empty array as a value causes an error (#715) (Nick Schot)
* [[`0f406b5908`](https://github.com/postlight/lux/commit/0f406b5908)] - **fix**: detailed error messages leak into production responses (#713) (Zachary Golba)
* [[`2327c13d1a`](https://github.com/postlight/lux/commit/2327c13d1a)] - **docs**: additional documentation around cli generators (#711) (Kyle MacDonald)
* [[`1691f9ad50`](https://github.com/postlight/lux/commit/1691f9ad50)] - **release**: 1.2.0 ✨ (#705) (Zachary Golba)

### v1.2.0 (May 16, 2017)

##### Commits
Expand Down
2 changes: 1 addition & 1 deletion bin/lux
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ cli
cli
.command('g <type> <name> [attrs...]')
.alias('generate')
.description('Example: lux generate model user')
.description('Example: lux generate model user name:string email:string admin:boolean')
.action((type, name, attrs) => {
exec('generate', { type, name, attrs })
.then(exit)
Expand Down
2 changes: 1 addition & 1 deletion examples/social-network/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"babel-preset-lux": "2.0.2",
"bcryptjs": "2.4.3",
"knex": "0.13.0",
"lux-framework": "1.2.0",
"lux-framework": "1.2.3",
"sqlite3": "3.1.8"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion examples/todo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"babel-core": "6.24.1",
"babel-preset-lux": "2.0.2",
"knex": "0.13.0",
"lux-framework": "1.2.0",
"lux-framework": "1.2.3",
"sqlite3": "3.1.8"
},
"devDependencies": {
Expand Down
16 changes: 16 additions & 0 deletions guide/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@ Use the new command to create your first project.
lux new <app-name>
```

### Generators

Lux allows you to use the CLI to generate boilerplate for the following types:

- `model`
- `controller`
- `serializer`
- `middleware`
- `migration`
- `resource`
- `util`

```bash
lux generate <type> <name> [attrs...]
```

### Running

To run your application use the serve command.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lux-framework",
"version": "1.2.0",
"version": "1.2.3",
"description": "Build scalable, Node.js-powered REST APIs with almost no code.",
"repository": "github:postlight/lux",
"keywords": [
Expand Down
32 changes: 17 additions & 15 deletions src/packages/database/model/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1166,20 +1166,6 @@ class Model {
const run = async (trx: Object) => {
const { hooks, logger, primaryKey } = this;
const instance = Reflect.construct(this, [props, false]);
let statements = [];

const associations = Object
.keys(props)
.filter(key => (
Boolean(this.relationshipFor(key))
));

if (associations.length) {
statements = associations.reduce((arr, key) => [
...arr,
...updateRelationship(instance, key, trx)
], []);
}

await runHooks(instance, trx, hooks.beforeValidation);

Expand All @@ -1191,12 +1177,28 @@ class Model {
hooks.beforeSave
);

const runner = createRunner(logger, statements);
const runner = createRunner(logger, []);
const [[primaryKeyValue]] = await runner(await create(instance, trx));

Reflect.set(instance, primaryKey, primaryKeyValue);
Reflect.set(instance.rawColumnData, primaryKey, primaryKeyValue);

let statements = [];
const associations = Object
.keys(props)
.filter(key => (
Boolean(this.relationshipFor(key))
));

if (associations.length) {
statements = associations.reduce((arr, key) => [
...arr,
...updateRelationship(instance, key, trx)
], []);
}

await Promise.all(statements);

Reflect.defineProperty(instance, 'initialized', {
value: true,
writable: false,
Expand Down
12 changes: 6 additions & 6 deletions src/packages/database/query/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,17 +195,17 @@ class Query<+T: any> extends Promise {
}

if (Array.isArray(value)) {
if (value.length > 1) {
this.snapshots.push([
not ? 'whereNotIn' : 'whereIn',
[key, value]
]);
} else {
if (value.length === 1) {
return {
...obj,
[key]: value[0]
};
}

this.snapshots.push([
not ? 'whereNotIn' : 'whereIn',
[key, value]
]);
} else if (value === null) {
this.snapshots.push([
not ? 'whereNotNull' : 'whereNull',
Expand Down
2 changes: 1 addition & 1 deletion src/packages/database/query/runner/utils/build-results.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export default async function buildResults<T: Model>({
.reduce((r, entry) => {
let [key, value] = entry;

if (!value && pkPattern.test(key)) {
if (value == null && pkPattern.test(key)) {
return r;
} else if (key.indexOf('.') >= 0) {
const [a, b] = key.split('.');
Expand Down
4 changes: 2 additions & 2 deletions src/packages/router/route/params/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function paramsFor({
if (method === 'POST' || method === 'PATCH') {
params = [
...params,
getDataParams(controller, true)
getDataParams(controller, method, true)
];
}
} else if (type === 'collection') {
Expand All @@ -45,7 +45,7 @@ export function paramsFor({
if (method === 'POST' || method === 'PATCH') {
params = [
...params,
getDataParams(controller, false)
getDataParams(controller, method, false)
];
}
} else if (type === 'custom') {
Expand Down
14 changes: 8 additions & 6 deletions src/packages/router/route/params/utils/get-data-params.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,18 @@ function getTypeParam({
/**
* @private
*/
function getAttributesParam({
model,
params
}: Controller): [string, ParameterLike] {
function getAttributesParam(
{ model, params }: Controller,
method: 'PATCH' | 'POST',
): [string, ParameterLike] {
return ['attributes', new ParameterGroup(params.reduce((group, param) => {
const col = model.columnFor(param);

if (col) {
const type = typeForColumn(col);
const path = `data.attributes.${param}`;
const required = !col.nullable && isNull(col.defaultValue);
const required =
method !== 'PATCH' && !col.nullable && isNull(col.defaultValue);

return [
...group,
Expand Down Expand Up @@ -140,13 +141,14 @@ function getRelationshipsParam({
*/
export default function getDataParams(
controller: Controller,
method: 'PATCH' | 'POST',
includeID: boolean
): [string, ParameterLike] {
let params = [getTypeParam(controller)];

if (controller.hasModel) {
params = [
getAttributesParam(controller),
getAttributesParam(controller, method),
getRelationshipsParam(controller),
...params
];
Expand Down
2 changes: 1 addition & 1 deletion src/packages/serializer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ class Serializer<T: Model> {
})
)
};
} else if (related && related.id) {
} else if (related && related.id != null) {
return this.formatRelationship({
domain,
included,
Expand Down
9 changes: 9 additions & 0 deletions src/packages/server/responder/test/responder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { createRequest } from '../../request';
import { createResponse } from '../../response';
import { createResponder } from '../index';

import setEnv from '../../../../../test/utils/set-env';
import { getTestApp } from '../../../../../test/utils/get-test-app';

const DOMAIN = 'http://localhost:4100';
Expand Down Expand Up @@ -198,6 +199,14 @@ describe('module "server/responder"', () => {
});

describe('- responding with an error', () => {
beforeEach(() => {
setEnv('development');
});

afterEach(() => {
setEnv('test');
});

it('works with vanilla errors', async () => {
const result = await test((req, res) => {
const respond = createResponder(req, res);
Expand Down
3 changes: 2 additions & 1 deletion src/packages/server/responder/utils/data-for.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @flow
import { VERSION } from '../../../jsonapi';
import { STATUS_CODES } from '../../constants';
import * as env from '../../../../utils/env';
import type { JSONAPI$Document, JSONAPI$ErrorObject } from '../../../jsonapi'; // eslint-disable-line max-len, no-duplicate-imports

/**
Expand All @@ -23,7 +24,7 @@ export default function dataFor(
errData.title = title;
}

if (err) {
if (err && env.isDevelopment()) {
errData.detail = err.message;
}

Expand Down
7 changes: 7 additions & 0 deletions src/utils/env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* @flow */

const isEnv = value => () => process.env.NODE_ENV === value;

export const isDevelopment: () => boolean = isEnv('development');
export const isProduction: () => boolean = isEnv('production');
export const isTest: () => boolean = isEnv('test');
25 changes: 25 additions & 0 deletions src/utils/test/env.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* @flow */

import { expect } from 'chai';
import { afterEach, test } from 'mocha';

import * as env from '../env';
import setEnv from '../../../test/utils/set-env';

afterEach(() => {
setEnv('test');
});

test('isDevelopment()', () => {
setEnv('development');
expect(env.isDevelopment()).to.be.true;
});

test('isProduction()', () => {
setEnv('production');
expect(env.isProduction()).to.be.true;
});

test('isTest()', () => {
expect(env.isTest()).to.be.true;
});
9 changes: 9 additions & 0 deletions test/utils/set-env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* @flow */

type Environment = 'development'
| 'production'
| 'test';

export default function setEnv(value: Environment): void {
global.process.env.NODE_ENV = value;
}