It's possible to automatically include relations to response in every operation (create, update, delete, ...) without an explicit "include" filter in url?
e.g.
When I send request:
path: '/api/users/12'
method: DELETE
It should respond with:
{
"id": "12",
"groupId": "10",
"group": {
"id": "10"
}
}
I end with this code, but it only include group relation for findAll and findOne methods:
module.exports = function(User) {
User.afterRemote('*', function(ctx, user, next) {
User.include(user, 'group', next);
});
};
Even when I update code to:
module.exports = function(User) {
User.afterRemote('*', function(ctx, user, next) {
User.app.models.groups.findOne({id: user.groupId}, function(err, group) {
user.group = group;
user.foo = 'bar';
next();
});
});
};
The response is:
{
"id": "12",
"foo": "bar", // "foo" was send, but "group" disappear
"groupId": "10"
}
Thank you.
Unfortunately, this does not work.
I'm not sure if I get it, but so far I tried:
// user.json
{
...
"scope": {
"include": "group"
}
}
but when I for example make POST request:
path: '/'
method: POST
data: {"groupId": "2"}
the "group" field in response is missing.
// actual
{
"id": "1",
"groupId": "2"
}
// expected
{
"id": "1",
"groupId": "2",
"group": {
"id": "2"
}
}
That's expected. The default scope won't come into the picture for the response of create. But subsequent queries will include the group.
to make it work, we use a remote after create hook where we put the
group relation in user.__data
Same pattern followed by User.login where the user can be returned within
an AccessToken.
@seriousben Thanks, I'll try to attach relation data in remote hook
For anyone else who stumbles here this is the solution I am working on. Currently tested with POST's and belongsTo relationships:
mixin: allownested.js
'use strict';
var Promise = require('bluebird');
module.exports = function (Model, options) {
Model.observe('before save', function event(ctx, next) {
var records = [];
Object.keys(Model.relations).forEach(function (related) {
var record = ctx.instance[related]();
if (typeof record !== 'undefined') {
records.push(ctx.instance[related].create(record).then(function (record) {
ctx.instance.set(related, record);
}));
}
});
Promise.all(records).then(function () {
return next();
});
});
};
Relevant parts of the model: company.js
"scope": {
"include": "address"
},
"mixins": {
"Allownested": true
},
"relations": {
"address": {
"type": "belongsTo",
"model": "Address",
"foreignKey": "addressId"
}
}
I will be extending this mixin to handle belongsTo, hasMany, hasOne. I will also ensure it works across Create, Update, Delete (Get already works like a champ)
Hi @twickstrom thank you for your message. Did you manage to make your code work across Create, Update, Delete? I tried it and it returns the nested models only while fetching entities, in particular the response of a Create returns only the ids of the nested properties. All my relations are "belongsTo"
Hey @danielesalvatore. So I changed a few things since this post. First, it seems as though changing the key names of relationships is not possible. If someone can point me in the right direction here I would appreciate it. (@raymondfeng perhaps). However I just added them with the relationship key name preceded by an _. Also I decided (for now to not support PUT/DELETE as there may be times where the related entity is not deleted or updated)
Here is an example:
Mixin: nested-create.js
'use strict';
var Promise = require('bluebird');
module.exports = function (Model, options) {
Model.observe('before save', function event(ctx, next) {
if(!!ctx.instance.isNewInstance) {
return next();
}
var records = [];
Object.keys(Model.relations).forEach(function (related) {
var key = Model.relations[related].keyTo,
type = Model.relations[related].type,
record = ctx.instance[related]();
if (typeof record !== 'undefined') {
if(!record[key]) {
records.push(ctx.instance[related].create(record).then(function (record) {
ctx.instance.setAttribute('_' + related, record);
}));
} else {
ctx.instance.setAttribute('_' + related, record);
}
}
});
Promise.all(records).then(function () {
return next();
});
});
};
Model: company.json
{
"name": "Company",
"description": "Generic company model",
"base": "PersistedModel",
"strict": true,
"options": {
"validateUpsert": true,
"allowEternalTokens": false
},
"dataSource": "sql",
"forceId": true,
"scope": {
"include": "address"
},
"mixins": {
"NestedCreate": true
},
"properties": {
"companyId": {
"type": "string",
"id": true,
"defaultFn": "uuidv4",
"length": 36
},
"name": {
"type": "string",
"required": true
},
"addressId": {
"type": "string",
"required": false,
"length": 36
}
},
"validations": [],
"relations": {
"address": {
"type": "belongsTo",
"model": "Address",
"foreignKey": "addressId"
},
"phonenumbers": {
"type": "hasAndBelongsToMany",
"model": "PhoneNumber",
"foreignKey": ""
}
},
"acls": [],
"methods": {}
}
Post: http://0.0.0.0:3000/companies/
Post Body:
{
"name": "Tim's Company",
"address": {
"address1": "127 Main St",
"address2": "Suite Now Promise 2",
"city": "Nasville",
"state": "TN",
"county": "Davidson",
"zip": "37215"
},
"phonenumbers": [
{"number": "888-555-1212"},
{"number": "888-555-1213"},
{"number": "888-555-1214"},
{"number": "888-555-1215"}
]
}
Response:
{
"companyId": "ec572afc-4885-4e64-bc61-cea13bdb070a",
"name": "Tim's Company",
"addressId": "88b1abc4-f92f-4efe-806f-a24aef280d51",
"_address": {
"addressId": "88b1abc4-f92f-4efe-806f-a24aef280d51",
"address1": "127 Main St",
"address2": "Suite Now Promise 2",
"city": "Nasville",
"state": "TN",
"county": "Davidson",
"zip": "37215"
},
"_phonenumbers": [
{
"phoneNumberId": "a30278b3-b9e6-4d2a-81a8-19ae0605a315",
"number": "888-555-1212"
},
{
"phoneNumberId": "f8f8a19d-9a1c-48b2-a795-20c14a7834d4",
"number": "888-555-1213"
},
{
"phoneNumberId": "859f78b0-f4db-419d-bcc2-802d196e032e",
"number": "888-555-1214"
},
{
"phoneNumberId": "756fc638-ee28-4c25-bb32-16a16e7cf583",
"number": "888-555-1215"
}
]
}
@raymondfeng
Can I ask if you know of a way of setting an attribute manually?
Example:
module.exports = function (Model, options) {
Model.observe('before save', function event(ctx, next) {
ctx.instance.setAttribute(related, record);
});
};
This works fine as long as _related_ equals a string that is not a name of a attribute or relationship on a model. If _related_ does equal a string that is an attribute or relationship on a model it is ignored.
I would like to force the inclusion if I can. Alternatively my work around prepends an _ to the related name, I wouldn't mind removing the _ prior to the response but so far have been unsuccessful in my attempts. Any pointers would be appreciated.
@twickstrom - Thanks for your reference code. As suggested by seriousben above you can set the attribute like this -
ctx.instance.__data[related] = record;
Below is the code which we enhanced for create and update of the hasMany and hasOne scenario for our case -
Model.observe('before save', (ctx, next) => {
ctx.hookState.relations = {};
if (ctx.isNewInstance && ctx.instance) {
Object.keys(Model.relations).forEach((related) => {
ctx.hookState.relations[related] = ctx.instance [related] ();
});
} else if (ctx.data) {
Object.keys(Model.relations).forEach((related) => {
ctx.hookState.relations[related] = ctx.data[related];
});
}
next();
});
Model.observe('after save', (ctx, next) => {
const promises = [];
if (ctx.instance) {
Object.keys(Model.relations).forEach((related) => {
const data = ctx.hookState.relations[related];
if (typeof data !== 'undefined') {
const relatedId = Model.relations[related].modelTo.getIdName();
let promise = Promise.resolve('ready');
if (data[relatedId]) {
promise = ctx.instance[related].update(data);
} else {
promise = ctx.instance[related].create(data);
}
promise = promise.then((record) => {
ctx.instance.__data[related] = record;
return ctx;
});
promises.push(promise);
}
});
}
Promise.all(promises).then(() => next()).catch(err => next(err));
});
Most helpful comment
Hey @danielesalvatore. So I changed a few things since this post. First, it seems as though changing the key names of relationships is not possible. If someone can point me in the right direction here I would appreciate it. (@raymondfeng perhaps). However I just added them with the relationship key name preceded by an _. Also I decided (for now to not support PUT/DELETE as there may be times where the related entity is not deleted or updated)
Here is an example:
Mixin: nested-create.js
Model: company.json
Post: http://0.0.0.0:3000/companies/
Post Body:
Response: