I have an emberjs application backed by a nodejs server and mongodb. Currently my database is sending documents with an '_id' field. I have the followign code to force Ember to treat '_id' as the primary key:
App.ApplicationSerializer = DS.RESTSerializer.extend({
primaryKey: '_id'
});
On the other hand i have two models related by a 'hasMany' relationship as such:
App.Player = DS.Model.extend({
name: DS.attr('string'),
thumbLink: DS.attr('string'),
activeGame: DS.belongsTo('game', { async: true }),
email: DS.attr('string'),
firstName: DS.attr('string'),
lastName: DS.attr('string'),
admin: DS.attr('boolean')
});
App.Game = DS.Model.extend({
name: DS.attr('string'),
active: DS.attr('boolean'),
players: DS.hasMany('player', { async: true })
});
The problem is that when i try to save the model ( this.get('model').save() )on my controller the ids are not serialized and the result is ember sending the following:
{"game":{"name":"Indie/Rock","active":false,"players":[],"_id":"53cbbf43daa978983ee0b101"}}
As you can see the players array is empty, and as a result, the server is saving that empty array which in fact is not correct. I am aware that it is possible to use { embedded: true } on the models and return the models with embedded documents from the server, but i want to preserve the async feature.
I have tried to extend the game serializer from EmbeddedRecordsMixing as the following:
App.GameSerializer = DS.ActiveModelSerializer
.extend(DS.EmbeddedRecordsMixin)
.extend({
attrs: {
players: {serialize: 'ids', deserialize: 'ids'},
}
});
But when i do so i get the following error from ember even though the ApplicationSerializer is suppossedly telling Ember to user _id as primary key:
Assertion Failed: Error: Assertion Failed: You must include an `id` for App.Game in a hash passed to `push`
My question is if it is possible to maintain the async features of ember-data while being able to serialize the document with the ids on it's relation and using _id as a primary key.
Thank you.
Ember Data is stupid in this aspect, if it's a ManyToOne relationship, it only includes the id from the belongsTo side. Honestly I've had it on my bucket list to submit a PR, but time is limited.
https://github.com/emberjs/data/commit/7f752ad15eb9b9454e3da3f4e0b8c487cdc70ff0#commitcomment-4923439
App.ApplicationSerializer = DS.RESTSerializer.extend({
serializeHasMany: function(record, json, relationship) {
var key = relationship.key;
var payloadKey = this.keyForRelationship ? this.keyForRelationship(key, "hasMany") : key;
var relationshipType = RelationshipChange.determineRelationshipType(record.constructor, relationship);
if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany'
|| relationshipType === 'manyToOne') { // This is the change
json[payloadKey] = get(record, key).mapBy('id');
// TODO support for polymorphic manyToNone and manyToMany relationships
}
},
});
Related
This is a contrived example of what I would like to do:
Suppose I have a database of teams and players:
team:
->id
->color
->rank
->division
player:
->id
->team_id
->number
->SECRET
And the following bookshelf models:
var Base = require('./base');
const Player = Base.Model.extend(
{
tableName: "players",
},
nonsecretdata: function() {
return this.belongsTo('Team')
},
{
fields: {
id: Base.Model.types.integer,
team_id: Base.Model.types.integer,
number: Base.Model.types.integer,
SECRET: Base.Model.types.string,
}
}
);
module.exports = Base.model('Player', Player);
And
var Base = require('./base');
const Team = Base.Model.extend(
{
tableName: "teams",
},
{
fields: {
id: Base.Model.types.integer,
color: Base.Model.types.string,
rank: Base.Model.types.integer,
division: Base.Model.types.string,
}
}
);
module.exports = Base.model('Team', Team);
My question is, how can I limit the scope of player such that SECRET is not grabbed by calls to join player and team with callback nonsecretdata?
I am new to Bookshelf so if any other information is needed, please let me know. Thank you
++++++++++
Edit: Do I need to create a separate model?
The only way to do this using bookshelf would be to delete the individual fields from the object after fetching the entire model.
A potentially better solution for this use case would be to define a custom Data Access Object class that uses a SQL query for the information that would like to be obtained and then use that DOA instead of using bookshelf. That way the SQL code is still abstracted away from the code that is requesting the information and the SECRET or any other potential sensitive information that is added to the table will not be included in the fetch.
I have a schema like this
const user = new Schema({
firstName: { type: String, required: true },
lastName: { type: String , required: true},
phone:{type: Number, unique true}
embeddedDocsAsJson: {} // not as an array
},
{ minimize: false }
)
I want to use embeddedDocsAsJson because of two reasons
In case of array a duplicate data can be pushed to array , if I use json it will not occur as I'll use unique id as json key
Retrieval will be faster as I don't have to iterate on the array . I can fetch it from the json key
Problem:
Firstly I'm inserting firstName and lastName phone.
And embeddedDocsAsJson is added while updating the docs below is my code for updating
let user = await User.findOne({phone: somenumber})
user.embeddedDocsAsJson.someId = someObject // getting error in this line because `user.embeddedDocsAsJson` is `undefined`
user.save()
I'm adding value to embeddedDocsAsJson while updating
EmbeddedDocs are by-default array if you want to save object in your collection below code will work.
let user = await User.findOne({phone: somenumber})
user.embeddedDocsAsJson = {}
user.embeddedDocsAsJson.someId = someObject // getting error in this line because `user.embeddedDocsAsJson` is `undefined`
user.save()
In my documents pre save hook, I check if all the properties from the nested property accident exist and have correct values. If accident does not exist at all this is also fine.
When I'm trying to save a doc which has no value on the nested property accident of my doc it still goes into my custom validation rules. This is because I fail to detect if this nested object is empty.
Property in the Schema
prescriptionSchema = mongoose.Schema({
accident: {
kind: String, #accident, workAccident,
date: Date,
company: String
},
...
Pre Save Hook
console.log _.isEqual(data.accident, {}) #false
console.log JSON.stringify data.accident #{}
console.log JSON.stringify data.accident == JSON.stringify {} #false
console.log JSON.stringify {} #{}
console.log Object.keys(data.accident).length #14
for key, value of data.accident
console.log key, value #gives me basically the whole document with functions etc.
Current Detection (not good code)
if data.accident? && (data.accident['kind'] || data.accident['date'] || data.accident['company']) #=> do validation
Seed
newRecipe = new RecipeModel()
for key, value of recipe
newRecipe[key] = value
newRecipe.save((err, result) ->
return next err if err?
return next "No Id" if !result._id?
return next null, result._id
)
I tried {}, null and nothing as values for recipe.accident.
Mongoose Version: 4.0.2
Node Version: 5.9
This solution worked although I now have _ids on the embedded objects which I don't need.
I moved out the object from the main schema and defined it as a own sub schema:
drugSchema = mongoose.Schema({
name: String,
exchangeable: Boolean
})
accidentSchema = mongoose.Schema({
kind: String, #accident, workAccident,
date: Date,
company: String
})
prescriptionSchema = mongoose.Schema({
drugs: [drugSchema],
dutiable: Boolean,
accident: accidentSchema,
...
After this, the object is undefined in the pre save hook.
By default, mongoose won't save empty objects. You need to disable the minimize option in your Schema.
prescriptionSchema = mongoose.Schema({
accident: {
kind: String, #accident, workAccident,
date: Date,
company: String
},
...
}, {
minimize: false
})
Source : http://mongoosejs.com/docs/guide.html#minimize
UPDATE : If it is not saving the object, you can try to use the markModified() method (http://mongoosejs.com/docs/schematypes.html#mixed).
And for your checking problem, you can perform a function which will check your fields. Here's two examples : https://jsfiddle.net/ew2um2dh/13/
I am new to node.js and newer to Sails.js framework.
I am currently trying to work with my database, I don't understand all the things with Sails.js but I manage to do what I want step by step. (I am used to some PHP MVC frameworks so it is not too difficult to understand the structure.)
Here I am trying to get a row from my database, using 2 JOIN clause. I managed to do this using SQL and the Model.query() function, but I would like to do this in a "cleaner" way.
So I have 3 tables in my database: meta, lang and meta_lang. It's quite simple and a picture being better than words, here are some screenshots.
meta
lang
meta_lang
What I want to do is to get the row in meta_table that match with 'default' meta and 'en' lang (for example).
Here are Meta and Lang models (I created them with sails generate model command and edited them with what I needed):
Meta
module.exports = {
attributes: {
code : { type: 'string' },
metaLangs:{
collection: 'MetaLang',
via: 'meta'
}
}
};
Lang
module.exports = {
attributes: {
code : { type: 'string' },
metaLangs:{
collection: 'MetaLang',
via: 'lang'
}
}
};
And here is my MetaLang model, with 3 functions I created to test several methods. The first function, findCurrent, works perfectly, but as you can see I had to write SQL. That is what I want to avoid if it is possible, I find it more clean (and I would like to use Sails.js tools as often as I can).
module.exports = {
tableName: 'meta_lang',
attributes: {
title : { type: 'string' },
description : { type: 'text' },
keywords : { type: 'string' },
meta:{
model:'Meta',
columnName: 'meta_id'
},
lang:{
model:'Lang',
columnName: 'lang_id'
}
},
findCurrent: function (metaCode, langCode) {
var query = 'SELECT ml.* FROM meta_lang ml INNER JOIN meta m ON m.id = ml.meta_id INNER JOIN lang l ON l.id = ml.lang_id WHERE m.code = ? AND l.code = ?';
MetaLang.query(query, [metaCode, langCode], function(err, metaLang) {
console.log('findCurrent');
if (err) return console.log(err);
console.log(metaLang);
// OK this works exactly as I want (I would have prefered a 'findOne' result, only 1 object instead of an array with 1 object in it, but I can do with it.)
});
},
findCurrentTest: function (metaCode, langCode) {
Meta.findByCode(metaCode).populate('metaLangs').exec(function(err, metaLang) {
console.log('findCurrentTest');
if (err) return console.log(err);
console.log(metaLang);
// I get what I expected (though not what I want): my meta + all metaLangs related to meta with code "default".
// What I want is to get ONE metaLang related to meta with code "default" AND lang with code "en".
});
},
findCurrentOthertest: function (metaCode, langCode) {
MetaLang.find().populate('meta', {where: {code:metaCode}}).populate('lang', {where: {code:langCode}}).exec(function(err, metaLang) {
console.log('findCurrentOthertest');
if (err) return console.log(err);
console.log(metaLang);
// Doesn't work as I wanted: it gets ALL the metaLang rows.
});
}
};
I also tried to first get my Meta by code, then my Lang by code, and MetaLang using Meta.id and Lang.id . But I would like to avoid 3 queries when I can have only one.
What I'm looking for would be something like MetaLang.find({meta.code:"default", lang.code:"en"}).
Hope you've got all needed details, just comment and ask for more if you don't.
Do you know what populate is for ? its for including the whole associated object when loading it from the database. Its practically the join you are trying to do, if all you need is row retrieval than quering the table without populate will make both functions you built work.
To me it looks like you are re-writing how Sails did the association. Id suggest giving the Associations docs another read in Sails documentation: Associations. As depending on your case you are just trying a one-to-many association with each table, you could avoid a middle table in my guess, but to decide better id need to understand your use-case.
When I saw the mySQL code it seemed to me you are still thinking in MySQL and PHP which takes time to convert from :) forcing the joins and middle tables yourself, redoing a lot of the stuff sails automated for you. I redone your example on 'disk' adapter and it worked perfectly. The whole point of WaterlineORM is to abstract the layer of going down to SQL unless absolutely necessary. Here is what I would do for your example, first without SQL just on a disk adapter id create the models :
// Lang.js
attributes: {
id :{ type: "Integer" , autoIncrement : true, primaryKey: true },
code :"string"
}
you see what i did redundantly here ? I did not really need the Id part as Sails does it for me. Just an example.
// Meta.js
attributes: {
code :"string"
}
better :) ?
// MetaLang.js
attributes:
{
title : "string",
desc : "string",
meta_id :
{
model : "meta",
},
lang_id :
{
model : "lang",
}
}
Now after simply creating the same values as your example i run sails console type :
MetaLang.find({meta_id : 1 ,lang_id:2}).exec(function(er,res){
console.log(res);
});
Output >>>
sails> [ { meta_id: 1,
lang_id: 2,
title: 'My blog',
id: 2 } ]
Now if you want to display what is meta with id 1 and what is lang with id 2, we use populate, but the referencing for join/search is just as simple as this.
sails> Meta_lang.find({meta_id : 1 ,lang_id:2}).populate('lang_id').populate('meta_id').exec(function(er,res){ console.log(res); });
undefined
sails> [ {
meta_id:
{ code: 'default',
id: 1 },
lang_id:
{ code: 'En',
id: 2 },
title: 'My blog',
id: 2 } ]
At this point, id switch adapters to MySQL and then create the MySQL tables with the same column names as above. Create the FK_constraints and voila.
Another strict policy you can add is to set up the 'via' and dominance on each model. you can read more about that in the Association documentation and it depends on the nature of association (many-to-many etc.)
To get the same result without knowing the Ids before-hand :
sails> Meta.findOne({code : "default"}).exec(function(err,needed_meta){
..... Lang.findOne({code : "En"}).exec(function(err_lang,needed_lang){
....... Meta_lang.find({meta_id : needed_meta.id , lang_id : needed_lang.id}).exec(function(err_all,result){
......... console.log(result);});
....... });
..... });
undefined
sails> [ { meta_id: 1,
lang_id: 2,
title: 'My blog',
id: 2 } ]
Have you tried:
findCurrentTest: function (metaCode, langCode) {
Meta.findByCode(metaCode).populate('metaLangs', {where: {code:langCode}}).exec(function(err, metaLang) {
console.log('findCurrentTest');
if (err) return console.log(err);
console.log(metaLang);
});
},
{username:'me', companies:{"yourcompany":{...}}
I want to insert a company into a user record (user collection), to make:
{username:'me', companies:{ "yourcompany":{...}, "mycompany":{...} }
But the name is dynamic..
var companyid = "mycompany";
.collection('users').findAndModify(
{username: usern},
[['_id', 'asc']],
{$set:{companies:{companyid: { desksmemberships:[] }}}},
{new: true}, function(){...}
Gives this.. {username:'me', companies:{ "yourcompany":{...}, "companyid":{...} }
How do I do this?
You'd have to build up your $set modifier programmatically:
var modifier = { $set: {} };
modifier.$set['companies.' + companyid] = { desksmemberships:[] };
And then use modifier as the third parameter in your findAndModify call.
You may also want to consider changing companies to be an array instead of an embedded object.
Node.js 4.x Update
You can now use the computed property syntax to do this directly in the object literal:
collection('users').findAndModify(
{username: usern},
[['_id', 'asc']],
{$set:{['companies.' + companyid]: { desksmemberships:[] }}},
{new: true},
function(){...});
I've seen this question on quite a few posts, some of them quite complex- call me crazy but you can do this (node + mongo):
My db schema 'matches' is set to an array. In the mongo data [match.matchId] becomes '2028856183'.
db.update({key: value}, {matches: [{[match.matchId] : match}] }, callback);
db.update(what to find, what to change it to, what to do next)
You can use any variable in the brackets.