I'm working with sails.js and mongo.db following some tutorials and creating a custom application and things are going well. Largely I'm using the built in, backbone I believe, scrud functions, I'm wondering how I could create a database entry from scratch. For example, the following works great from form data in my Student Controller:
create: function (req, res, next) {
Student.create(req.params.all(), function studentCreated(err, student) {
if (err) {
console.log(err);
req.session.flash = {
err: err
}
return res.redirect('/');
}
res.redirect('/');
});
},
For simplicity my Student model currently just has a first name and a one way association with a schools model.
module.exports = {
attributes: {
school: {
model: 'school'
},
first_name: {
type: 'string'
},
},
};
Say I wanted to, for the sake of understanding, just create a student with a fixed first name "Bob" and fixed school Id "xyz" in another action, without using the built in backbone create functions, nor using defaultTo in the model, nor using any form data. I would like to just code creating a database entry in an action, for example a test action in my Student controller. How would I go about this? I tried googling this a little, but given it's beginner nature I'm sure my query parameters were not particularly good.
If I correctly understand the question, I think you've already done most of the work. In the Student.create() call, replace req.params.all() by {first_name:'Bob', school:'xyz'}.
If you want to mix it with values from the request params, you could use {first_name:'bob', school: req.param('school')}
Related
I've got a Page:
const PageSchema = new Schema({
children: [{type: mongoose.Schema.Types.ObjectId, ref: 'Page'}]
});
As you can see each Page has an array of children which is also a Page.
Now what I need to do is fetch all "main" pages and populate their children array, easy enough except for the fact that I need to do this recursively since a Page contains an array of Pages.
MongoDB doesn't have any out of the box support for this, it only supports a 2 level deep population.
Here's my current query (removed all extra stuff for readability) without using the current .populate method (since it's not gonna work anyway):
Page.find(query)
.exec((err, pages) => {
if (err) {
return next(err);
}
res.json(pages);
});
I looked at this question which is similar but not exactly what I need:
mongoose recursive populate
That seems to use a parent to populate recursively and it also starts from just 1 document, rather than my scenario which uses an array of documents since I'm using .find and not .findOne for example.
How can I create my own deep recursive populate function for this?
Sidenote:
I am aware that the solution I need isn't recommended due to performance but I've come to the conclusion that it is the only solution that is going to work for me. I need to do recursive fetching regardless if it's in the frontend or backend, and doing it right in the backend will simplify things massively. Also the number of pages won't be big enough to cause performance issues.
You can recursively populate a field also like:
User.findOne({ name: 'Joe' })
.populate({
path: 'blogPosts',
populate: {
path: 'comments',
model: 'comment',
populate: {
path: 'user',
model: 'user'
}
}
})
.then((user) => {});
Please note that for first population, you don't need to specify model attribute, as it is already defined in your model's schema, but for next nested populations, you need to do that.
The answer actually lied in one of the answers from the previous questions, although a bit vague. Here's what I ended up with and it works really well:
Page.find(query)
.or({label: new RegExp(config.query, 'i')})
.sort(config.sortBy)
.limit(config.limit)
.skip(config.offset)
.exec((err, pages) => {
if (err) {
return next(err);
}
// takes a collection and a document id and returns this document fully nested with its children
const populateChildren = (coll, id) => {
return coll.findOne({_id: id})
.then((page) => {
if (!page.children || !page.children.length) {
return page;
}
return Promise.all(page.children.map(childId => populateChildren(coll, childId)))
.then(children => Object.assign(page, {children}))
});
}
Promise.all(pages.map((page) => {
return populateChildren(Page, page._id);
})).then((pages) => {
res.json({
error: null,
data: pages,
total: total,
results: pages.length
});
});
});
The function itself should be refactored into a utils function that can be used anywhere and also it should be a bit more general so it can be used for other deep populations as well.
I hope this helps someone else in the future :)
I've been working on this for hours and I'm completely lost, because the loopback documentation is not helpful.
I'm trying to write application logic into a model. The documentation for that is here. Unfortunately, the example doesn't demonstrate anything useful other than passing an external value into the remote method and returning it again. I'd like to understand how to run a query in this context and access model data, but I have searched for hours and not been able to find documentation on even these simple tasks. Maybe I'm just looking in the wrong places. Can anyone help?
Typically, you can accomplish most things you'd want to do such as querying and accessing model data (CRUD operations) through the built-in methods that all models get; see http://docs.strongloop.com/display/LB/Working+with+data. Defining a remote method (custom REST endpoint) for these would be redundant.
You access the standard model CRUD Node APIs (e.g. myModel.create(), myModel.find(), myModel.updateAll() ) in the remote method code if you want to.
You may also find further related examples in https://github.com/strongloop/loopback-example-app-logic
Here's an example using the Getting Started app https://github.com/strongloop/loopback-getting-started app. It defines a remote method that takes a number arg and prints the name of the coffeeshop with that ID to the console:
This code is in common/models/coffeeshop.js:
module.exports = function(CoffeeShop) {
...
// Return Coffee Shop name given an ID.
CoffeeShop.getName = function(shopId, cb) {
CoffeeShop.findById( shopId, function (err, instance) {
response = "Name of coffee shop is " + instance.name;
cb(null, response);
console.log(response);
});
}
...
CoffeeShop.remoteMethod (
'getName',
{
http: {path: '/getname', verb: 'get'},
accepts: {arg: 'id', type: 'number', http: { source: 'query' } },
returns: {arg: 'name', type: 'string'}
}
);
};
You can use the API Explorer to load http://0.0.0.0:3000/explorer/#!/CoffeeShops/getName then enter a number (there are only three coffee shops in the app initially) as the query param and hit "Try It Out!"
Or just GET a URL like http://0.0.0.0:3000/api/CoffeeShops/getid?id=1
Rand
I finally discovered my problem. Object properties must be loaded in a callback of the function calling the CRUD operation. The following syntax worked for me:
module.exports = function (TestModel) {
TestModel.testRemoteMethod = function (id, name, cb) {
TestModel.findOne({where: {id: id}}, function(err, modelInstance) {
//modelInstance has properties here and can be returned to
//the API call using the callback, for example:
cb(null, {"name": modelInstance.name});
}
}
TestModel.remoteMethod('testRemoteMethod',
//..rest of config
I have an app that stores user uploaded spreadsheets as tables in PostgreSQL. Everytime an user uploads a spreadsheet I create a record in a Dataset table containing the physical table name, its alias and the owner. I can retrieve a certain Dataset information with
GET domain.com/v1/Datasets/{id}
AFAIK, the relation between rows in Dataset and physical tables can't be enforced by a FK, or at least I haven't seen anyones creating FKs on the information_schema of PostgreSQL, and FKs can't drop tables, or can they? So it's common to have orphan tables, or records in Dataset that point to tables that no longer exist. I have managed this with business logic and cleaning tasks.
Now, to access one of those physical tables, for example one called nba_teams I would need to declare an NbaTeams model in loopback and restart the app, then query its records with
GET domain.com/v1/NbaTeams/{id}
But that can't scale, specially if I'm already having like 100 uploads a day. So from where I'm standing, there are two ways to go:
1.- Create one model, then add 4 custom methods that accepts a table name as a string, and perform the next CRUD operation on that table name via raw queries. For example, to list the records:
GET domain.com/v1/Datasets/getTable/NbaTeams
or, to update one team
PUT domain.com/v1/Datasets/getTable/NbaTeams/{teamId}
This sounds unelegant but should work.
2.- Create a custom method that accepts a table name as a string, which in turn creates an ephemeral model and forward the HTTP verb and the rest of the arguments to it
dataSource.discoverAndBuildModels('nba_teams', {
owner: 'uploader'
}, function (err, models) {
console.log(models);
models.NbaTeams.find(function (err, act) {
if (err) {
console.error(err);
} else {
console.log(act);
}
dataSource.disconnect();
});
});
this second one I haven't got to work yet, and I don't know how much overhead it might have, but I'm sure it's doable.
So before I dig in deeper I came to ask: has anybody dealt with this row-to-table relation? What are the good practices in this?
In the end, I did my own hacky workaround and I thought it may help someone, some day.
What I did was put a middleware (with regular express syntax) to listen for /v1/dataset{id_dataset} , create the model on the fly and pass the execution to the next middleware
app.use('/v1/dataset:id_dataset', function(req, res, next) {
var idDataset=req.params.id_dataset;
app.getTheTable(idDataset,function(err,result) {
if(err) {
console.error(err);
res.json({"error":"couldn't retrieve related table"});
} else {
next();
}
});
});
inside the app.getTheTable function, I'm creating a model dynamically and setting it up before callback
app.getTheTable = function (idDataset, callback) {
var Table = app.models.Dataset,
modelName='dataset'+idDataset,
dataSource;
Table.findById(idDataset, function (err, resultados) {
if (err) {
callback(new Error('Unauthorized'));
} else {
if(app.models[modelName]) {
callback(null,modelName); // model already exists
} else {
var theDataset = dataSource.createModel(modelName, properties, options);
theDataset.settings.plural = modelName;
theDataset.setup();
app.model(theDataset);
var restApiRoot = app.get('restApiRoot');
app.use(restApiRoot, app.loopback.rest());
callback(null, modelName);
}
}
});
};
It's hacky, I know, and I believe there must be some kind of performance penalty for overloading restApiRoot middleware, but it's still better tan creating 500 models on startup to cover all possible dataset requests.
I have a basic Mongoose model with a Meeting and Participants array:
var MeetingSchema = new Schema({
description: {
type: String
},
maxNumberOfParticipants: {
type: Number
},
participants: [ {
type: Schema.ObjectId,
ref: 'User'
} ]
});
Let's say I want to validate that the number of participants added doesn't exceed the maxNumberOfParticipants for that meeting.
I've thought through a few options:
Custom Validator - which I can't do because I have to validate one attribute (participants length) against another (maxNumberOfParticipants).
Middleware - i.e., pre-save. I can't do this either because my addition of participants occurs via a findOneAndUpdate (and these don't get called unless I use save).
Add validation as part of my addParticipants method. This seems reasonable, but I'm not sure how to pass back a validation error from the model.
Note that I don't want to implement the validation in the controller (express, MEAN.js stack) because I'd like to keep all logic and validations on the model.
Here is my addParticipants method:
MeetingSchema.methods.addParticipant = function addParticipant(params, callback) {
var Meeting = mongoose.model('Meeting');
if (this.participants.length == this.maxNumberOfParticipants) {
// since we already have the max length then don't add one more
return ????
}
return Meeting.findOneAndUpdate({ _id: this.id },
{ $addToSet: { participants: params.id } },
{new: true})
.populate('participants', 'displayName')
.exec(callback);
};
Not sure how to return a validation error in this case or even if this pattern is a recommended approach.
I wouldn't think that's it's common practice for this to be done at the mongoose schema level. Typically you will have something in between the function getting called and the database layer (your schema) that performs some kind of validation (such as checking max count). You would want your database layer to be in charge of just doing simple/basic data manipulation that way you don't have to worry about any extra dependencies when/if anything else calls it. This may mean you'd need to go with route 1 that you suggested, yes you would need to perform a database request to find out what your current number of participants but I think it the long run it will help you :)
Working on a project in KeystoneJS and I'm having trouble figuring out the mongoose relationship bit.
According to the keystone docs, let's say we have the following models: User and Post. Now a post has a relationship to a user, so I'll write:
Post.add({
author: { type: Types.Relationship, ref: 'User' }
});
and then:
User.relationship({ path: 'posts', ref: 'Post', refPath: 'author' });
Now, I want to be able to see all posts related to that User without having to query for both a User and Posts. For example, if I queried for a user object I would like to be able to do user.posts and have access to those related posts. Can you do this with mongoose/keystone?
As far as I understand, keystone's List Relationship has nothing to do with mongoose and querying. Instead, it is used by keystone's admin UI to build out the relationship queries before rendering them in the view. This said I would forget User.relationship(...); solving your problem, although you want it for what I just mentioned.
The following should work fine based on your schema, but only populates the relationship on the one side:
var keystone = require('keystone');
keystone.list('Post').model.findOne().populate('author', function (err, doc) {
console.log(doc.author.name); // Joe
console.log(doc.populated('author')); // '1234af9876b024c680d111a1' -> _id
});
You could also try this the other way, however...
keystone.list('User').model.findOne().populate('posts', function (err, doc) {
doc.posts.forEach(function (post) {
console.log(post);
});
});
...mongoose expects that this definition is added to the Schema. This relationship is added by including this line in your User list file:
User.schema.add({ posts: { type: Types.Relationship, ref: 'Post', many: true } })
After reading the keystone docs, this seems to be logically equivalent the mongoose pure way, User.schema.add({ posts: [{ type: Schema.Types.ObjectId, ref: 'Post' }] });. And now you are now maintaining the relationship on both lists. Instead, you may want to add a method to your keystone list.
User.schema.methods.posts = function(done){
return keystone.list('Post').model.find()
.where('author', this.id )
.exec(done);
};
By adding a method to your User list, it saves you from persisting the array of ObjectIds relating the MongoDB document back to the Post documents. I know this requires a second query, but one of these two options look to be your best bet.
I found this on their github https://github.com/Automattic/mongoose/issues/1888, check it for context, but basically says to use the keystone populateRelated() method. I tested it and does work
// if you've got a single document you want to populate a relationship on, it's neater
Category.model.findOne().exec(function(err, category) {
category.populateRelated('posts', function(err) {
// posts is populated
});
});
I'm aware the question is old but this has to be out there for further reference