bookshelf.js equivalent of Rails default scope (in node.js) - node.js

Is there a bookshelf.js (http://bookshelfjs.org/) equivalent to the 'default scope' in Rails?
For example, lets say I have the model "User":
User = Knex.Model.extend({
tableName: 'users'
});
Let's also say that the user's table has a boolean field called "verified". What I want to know is if there is a way to have a default scope such that when I query the users table,
collection.fetch().then(function(collection) {
// the collection contains only users with the verified = true attribute
})
the collection returned contains only users with the verified = true attribute

I just build an NPM package that does this using a bookshelf plugin called bookshelf-scopes.
var knex = require('knex')({
client: 'sqlite3',
connection: { filename: "./mytestdb" }
});
var bookshelf = require('bookshelf')(knex);
bookshelf.plugin(require('bookshelf-scopes'));
Now in your model you can add scopes.
var TestModel1 = bookshelf.Model.extend({
tableName: 'testmodel',
scopes: {
active: function(qb) {
qb.where({status: 'Active'});
}
}
});

Bookshelf model (or any backbone model) allow extending both instance properties and classProperties.
classProperties attaches directly to the constructor function which is similar to ActiveRecord's scope methods.
You can add verified method to User model's class properties like this.
User = Bookshelf.Model.extend({
tableName: 'users'
}, {
verified: function (options) {
options = options || {};
return Db.Collection.forge([], {model: this}).query(function (qb) {
qb.where('verified', true);
}).fetch(options);
}
});
And use it like this.
User.verified().then(function(collection) {
// the collection contains only users with the verified = true attribute
});

This is something on the roadmap, for now you can do something like:
collection.query('where', 'verified', '=', true).fetch().then(function(collection) {
// the collection contains only users with the verified = true attribute
});

you could also simply cache the partial query like
var default_query = collection.query('where', 'verified', '=', true);
and then always use that, I guess?

Related

How to create middlawere for mongoose queries

I have the legacy backend. This backend use mongoose. There is collection "Users". This collection has "email" field.
I was faced with the bug - email is case sensitive. I want to save all new emails in lower case. For this I've modified "Users" schema with this:
email: {
type: String,
lowercase: true
}
But also backend has a lot of places with things like:
user.find({email: some_case_unnormalized_email})
or this:
user.findOne({email: some_case_unnormalized_email})
I don't want to modify that places to:
user.find({email: some_case_unnormalized_email.toLowerCase()})
Instead, I want to create some middleware for "Users" schema like:
UsersSchema.middleware('any_query', queryObject => {
if (queryObject.email) {
queryObject.email = queryObject.email.toLowerCase();
}
});
How to do that?
Use pre save hook to modify any property
UserSchema.pre('find', function(next){
var query = this;
// console.log(JSON.stringify(query, null, 2));
// update your query here
next();
})
UserSchema.pre('findOne', function(next){
var query = this;
// console.log(JSON.stringify(query, null, 2));
// update your query here
next();
})

Bookshelf.js Registry [Error: The model ... could not be resolved from the registry plugin.]

I'm trying to use the registry plugin with Bookshelf.js because I think I am encountering a circular dependency, but I am getting nowhere with it.
Before I was getting this error, I was getting an error:
[Error: A valid target model must be defined for the category hasMany relation]
I have a Category and Tag model, but I keep getting the error
[Error: The model Tag could not be resolved from the registry plugin.]
My Category model looks like:
var knex = require('knex')(config.knex);
var bookshelf = require('bookshelf')(knex);
bookshelf.plugin('registry');
var Category = bookshelf.Model.extend({
tableName: 'category',
tag: function() {
return this.hasMany('Tag', 'category_id');
}
});
module.exports = bookshelf.model('Category', Category);
and my Tag model looks like
var knex = require('knex')(config.knex);
var bookshelf = require('bookshelf')(knex);
bookshelf.plugin('registry')
var Tag = bookshelf.Model.extend({
tableName: 'tag',
category: function() {
return this.belongsTo('Category');
},
events: function() {
return this.belongsToMany(Event, 'event_tag');
},
});
module.exports = bookshelf.model('Tag', Tag);
I have no idea where I am going wrong, it is so frustrating. Can anyone help please?????
this.hasMany uses table names or Bookshelf Object names. You want to use 'tag' (table name) or Tag (Bookshelf Object name) but not 'Tag' (not a table name).
So use this :
tag: function() {
return this.hasMany('tag', 'category_id');
}
Or this :
tag: function() {
return this.hasMany(Tag, 'category_id');
}
You will have the same problem in your Tag model :
category: function() {
return this.belongsTo(Category); // (or 'category')
},
This error also exist when the corresponding model is not registerd into the registry object of Bookshelf.
A Quick Fix for this to use the below statement explicitly after extending model -
bookshelf.resolve = () => DocumentEditionValueModel;
It will register this model to the bookshelf registry object.

Objects returned by Mongoose queries have no attributes

I currently have 3 MongoDB databases to which I connect from a Node.js app using mongoose.createConnection(...). For each db, I define schemas and models for all collections in the db. The problem I have is that when I query a collection, the results returned do not have any attributes set. However, using node-inspector, I can see that the attributes are loaded correctly from the db because they are present in the _doc attribute.
Example (some code is omitted):
var mongoose = require('mongoose');
// Connect to a db
var db = mongoose.createConnection();
var options = { auto_reconnect: true };
db.open(args.host, args.db, args.port, options, function(error, connection) {
var buildModel = require('../models/' + dbName + '/schema.js');
buildModel(db);
}
// Define schemas and models (in schema.js). This is the `buildModel` function from above.
module.exports = function(mongoose) {
var Group = new Schema({
name: { type: String, required: true },
companyId: { type: ObjectId, required: true }
});
mongoose.model("Group", Group, 'groups');
};
// Querying
var Group = getDb('db1').model('Group');
Group.find({}, function(error, groups) {
// Here I get all documents in the groups collection, but the attributes
// name and companyId are not set.
groups.forEach(function(group) {
// name and companyId are undefined
console.log('undefined' == typeof group.name); // true
console.log('undefined' == typeof group.companyId); // true
// _doc attribute is populated
console.log(group._doc.name); // 'Group 1'
});
});
The question is, am I forgetting to do something when I connect? I have also tried to specify the attributes to fetch using populate after calling find, but with no success.
I am using MongoDB 2.4.3, Node.js 0.10.6 and Mongoose 3.6.11.

Dynamically create collection with Mongoose

I want to give users the ability to create collections in my Node app. I have really only seen example of hard coding in collections with mongoose. Anyone know if its possible to create collections dynamically with mongoose? If so an example would be very helpful.
Basically I want to be able to store data for different 'events' in different collections.
I.E.
Events:
event1,
event2,
...
eventN
Users can create there own custom event and store data in that collection. In the end each event might have hundreds/thousands of rows. I would like to give users the ability to perform CRUD operations on their events. Rather than store in one big collection I would like to store each events data in a different collection.
I don't really have an example of what I have tried as I have only created 'hard coded' collections with mongoose. I am not even sure I can create a new collection in mongoose that is dynamic based on a user request.
var mongoose = require('mongoose');
mongoose.connect('localhost', 'events');
var schema = mongoose.Schema({ name: 'string' });
var Event1 = mongoose.model('Event1', schema);
var event1= new Event1({ name: 'something' });
event1.save(function (err) {
if (err) // ...
console.log('meow');
});
Above works great if I hard code 'Event1' as a collection. Not sure I create a dynamic collection.
var mongoose = require('mongoose');
mongoose.connect('localhost', 'events');
...
var userDefinedEvent = //get this from a client side request
...
var schema = mongoose.Schema({ name: 'string' });
var userDefinedEvent = mongoose.model(userDefinedEvent, schema);
Can you do that?
I believe that this is a terrible idea to implement, but a question deserves an answer. You need to define a schema with a dynamic name that allows information of 'Any' type in it. A function to do this may be a little similar to this function:
var establishedModels = {};
function createModelForName(name) {
if (!(name in establishedModels)) {
var Any = new Schema({ any: Schema.Types.Mixed });
establishedModels[name] = mongoose.model(name, Any);
}
return establishedModels[name];
}
Now you can create models that allow information without any kind of restriction, including the name. I'm going to assume an object defined like this, {name: 'hello', content: {x: 1}}, which is provided by the 'user'. To save this, I can run the following code:
var stuff = {name: 'hello', content: {x: 1}}; // Define info.
var Model = createModelForName(name); // Create the model.
var model = Model(stuff.content); // Create a model instance.
model.save(function (err) { // Save
if (err) {
console.log(err);
}
});
Queries are very similar, fetch the model and then do a query:
var stuff = {name: 'hello', query: {x: {'$gt': 0}}}; // Define info.
var Model = createModelForName(name); // Create the model.
model.find(stuff.query, function (err, entries) {
// Do something with the matched entries.
});
You will have to implement code to protect your queries. You don't want the user to blow up your db.
From mongo docs here: data modeling
In certain situations, you might choose to store information in
several collections rather than in a single collection.
Consider a sample collection logs that stores log documents for
various environment and applications. The logs collection contains
documents of the following form:
{ log: "dev", ts: ..., info: ... } { log: "debug", ts: ..., info: ...}
If the total number of documents is low you may group documents into
collection by type. For logs, consider maintaining distinct log
collections, such as logs.dev and logs.debug. The logs.dev collection
would contain only the documents related to the dev environment.
Generally, having large number of collections has no significant
performance penalty and results in very good performance. Distinct
collections are very important for high-throughput batch processing.
Say I have 20 different events. Each event has 1 million entries... As such if this is all in one collection I will have to filter the collection by event for every CRUD op.
I would suggest you keep all events in the same collection, especially if event names depend on client code and are thus subject to change. Instead, index the name and user reference.
mongoose.Schema({
name: { type: String, index: true },
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', index: true }
});
Furthermore I think you came at the problem a bit backwards (but I might be mistaken). Are you finding events within the context of a user, or finding users within the context of an event name? I have a feeling it's the former, and you should be partitioning on user reference, not the event name in the first place.
If you do not need to find all events for a user and just need to deal with user and event name together you could go with a compound index:
schema.index({ user: 1, name: 1 });
If you are dealing with millions of documents, make sure to turn off auto index:
schema.set('autoIndex', false);
This post has interesting stuff about naming collections and using a specific schema as well:
How to access a preexisting collection with Mongoose?
You could try the following:
var createDB = function(name) {
var connection = mongoose.createConnection(
'mongodb://localhost:27017/' + name);
connection.on('open', function() {
connection.db.collectionNames(function(error) {
if (error) {
return console.log("error", error)
}
});
});
connection.on('error', function(error) {
return console.log("error", error)
});
}
It is important that you get the collections names with connection.db.collectionNames, otherwise the Database won't be created.
This method works best for me , This example creates dynamic collection for each users , each collection will hold only corresponding users information (login details), first declare the function dynamicModel in separate file : example model.js
/* model.js */
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
function dynamicModel(suffix) {
var addressSchema = new Schema(
{
"name" : {type: String, default: '',trim: true},
  "login_time" : {type: Date},
"location" : {type: String, default: '',trim: true},
}
);
return mongoose.model('user_' + suffix, addressSchema);
}
module.exports = dynamicModel;
In controller File example user.js,first function to create dynamic collection and second function to save data to a particular collection
/* user.js */
var mongoose = require('mongoose'),
function CreateModel(user_name){//function to create collection , user_name argument contains collection name
var Model = require(path.resolve('./model.js'))(user_name);
}
function save_user_info(user_name,data){//function to save user info , data argument contains user info
var UserModel = mongoose.model(user_name) ;
var usermodel = UserModel(data);
usermodel.save(function (err) {
if (err) {
console.log(err);
} else {
console.log("\nSaved");
}
});
}
yes we can do that .I have tried it and its working.
REFERENCE CODE:
app.post("/",function(req,res){
var Cat=req.body.catg;
const link= req.body.link;
const rating=req.body.rating;
Cat=mongoose.model(Cat,schema);
const item=new Cat({
name:link,
age:rating
});
item.save();
res.render("\index");
});
I tried Magesh varan Reference Code ,
and this code works for me
router.post("/auto-create-collection", (req, res) => {
var reqData = req.body; // {"username":"123","password":"321","collectionName":"user_data"}
let userName = reqData.username;
let passWord = reqData.password;
let collectionName = reqData.collectionName;
// create schema
var mySchema = new mongoose.Schema({
userName: String,
passWord: String,
});
// create model
var myModel = mongoose.model(collectionName, mySchema);
const storeData = new myModel({
userName: userName,
passWord: passWord,
});
storeData.save();
res.json(storeData);
});
Create a dynamic.model.ts access from some where to achieve this feature.
import mongoose, { Schema } from "mongoose";
export default function dynamicModelName(collectionName: any) {
var dynamicSchema = new Schema({ any: Schema.Types.Mixed }, { strict: false });
return mongoose.model(collectionName, dynamicSchema);
}
Create dynamic model
import dynamicModelName from "../models/dynamic.model"
var stuff = { name: 'hello', content: { x: 1 } };
var Model = await dynamicModelName('test2')
let response = await new Model(stuff).save();
return res.send(response);
Get the value from the dynamic model
var Model = dynamicModelName('test2');
let response = await Model.find();
return res.send(response);

Mongoose + Express - What's the proper way to save the user in the document (audit info)

I'm writing an Express.js app that uses Mongoose ODM.
When a document is created/updated, I want it to automatically get some fields populated:
createdBy
createdOn
It seems to me that the right place to implement that would be in a Mongoose plugin that augments the document with those properties and provides defaults and/or mongoose middleware to populate the fields.
However, I have absolutely no idea how I could get the username from the session in my plugin.
Any suggestion?
/**
* A mongoose plugin to add mandatory 'createdBy' and 'createdOn' fields.
*/
module.exports = exports = function auditablePlugin (schema, options) {
schema.add({
createdBy: { type: String, required: true, 'default': user }
, createdOn: { type: Date, required: true, 'default': now }
});
};
var user = function(){
return 'user'; // HOW to get the user from the session ???
};
var now = function(){
return new Date;
};
You can't do this like this, bacause user function is added as a prototype method for this schema. In particular it is request/response independent. There is a hack however. If you have object obj of the type schema, then you can do
obj.session = req.session;
in request handler. Then you can access the session from the function. However this may lead to other issues ( for example running cron jobs on this collection ) and it looks like a very bad practice to me.
You can just do that manually, when a current user creates this schema object, can't you? Or create static method:
MySchema.statics.createObject = function ( user ) {
// create your object
var new_object = MySchema( ... );
// set necessary fields
new_object.createdBy = user.username;
}

Resources