How do you remove a model from mongoose? - javascript

I don't mean remove a document or documents. I mean remove the model entirely so that mongoose is no longer aware of it. After declaring a model I can't figure out how to make mongoose forget that model so that it could be recreated.
mongoose.model('Book', bookSchema);
mongoose.model('Book', bookSchema);
Currently the above throws an exception.
OverwriteModelError: Cannot overwrite 'Book' model once compiled.
I'd like to be able do something like this...
mongoose.model('Book', bookSchema);
mongoose.removeModel('Book');
mongoose.model('Book', bookSchema);
...and not get any errors. Any ideas?

try this
delete mongoose.connection.models['Book'];
and then re-register/re-initialize it . it will work fine

It appears that you'd have to overwrite some of the source code in order to be able to remove a model an add a new one since Mongoose makes sure that a model doesn't exist before it's willing to create a new one, which may or may not be more than you care to do:
if (this.models[name] && !collection) {
// model exists but we are not subclassing with custom collection
if (schema instanceof Schema && schema != this.models[name].schema) {
throw new MongooseError.OverwriteModelError(name);
}
return this.models[name];
}
Line 587 https://github.com/LearnBoost/mongoose/blob/master/lib/connection.js
Question Author's Update:
Thanks to this answer I discovered that you can access the models defined on the connection through connection.models. In my scenario I was testing a mongoose plugin with Mocha and and I wanted to clear the models between each unit test.
afterEach(function () {
mongoose.connection.models = {};
});

You can use the deleteModel method
mongoose.deleteModel(modelName);

This is actually a better way to get rid of models, schemas and collections in mongoose
mongoose.connections.forEach(connection => {
const modelNames = Object.keys(connection.models)
modelNames.forEach(modelName => {
delete connection.models[modelName]
})
const collectionNames = Object.keys(connection.collections)
collectionNames.forEach(collectionName => {
delete connection.collections[collectionName]
})
})
const modelSchemaNames = Object.keys(mongoose.modelSchemas)
modelSchemaNames.forEach(modelSchemaName => {
delete mongoose.modelSchemas[modelSchemaName]
})
Reference: https://github.com/Automattic/mongoose/issues/2874#issuecomment-388588452

Related

Create and run migrations in run time with Knex

I'm using strapi alongside knex, however strapi doesn't create any migrations and when you edit a content type (Model) by deleting a property or renaming it, it doesn't delete the targeted property, it just creates a new one, and when you delete a model it doesn't actually delete the table on the database.
This is really annoying because you'll end up with a lot of redundancies and unused tables and fields while developing.
What I'm doing is overriding the Strapi controllers that are responsible for creating, removing or updating a model and I want to be able to create a migration with the new schema on those controllers and then run it so I keep my database clean and up-to-date with strapi's models.
I know about the existence of knex.migrate.make however it doesn't seem to address my issue and the docs don't really tell you how to provide the migration fields to that function.
I was able to also find this http://knexjs.org/#custom-migration-sources however once again the docs are not very clear on what options I can provide and how to solve my issue.
Ideally I would want to end up with something like:
updateModel({attributes, table}){
const updatedSchena = attributes
knex.migrate.make(`update_${table.name}_table`, updatedSchema);
}
Edit:
I was able to create the new migration at run time but the contents of the file remain empty.
This is what I have so far
New custom-migration-sources as indicated in the docs:
class MyMigrationSource {
// Must return a Promise containing a list of migrations.
// Migrations can be whatever you want, they will be passed as
// arguments to getMigrationName and getMigration
constructor(name, attributes) {
this.name = name
this.attributes = attributes
console.log('attributes: ', attributes)
}
getMigrations() {
// In this example we are just returning migration names
return Promise.resolve(['migration1'])
}
getMigrationName(migration) {
return migration;
}
getMigration() {
return {
up(knex) {
//const migrate = require('widget-knex-schema')
return migrate.createTable(knex, this.name, this.attributes)
},
down(knex) {
return knex.schema.dropTableIfExists(this.name)
}
}
}
}
Instantiate the new migration source
const migration = new MyMigrationSource(modelJSON.collectionName, formatedAttributes)
Create the new migration with the custom configuration:
knex.migrate.make(`update_${name}_table`, {
migrationSource: migration
})
And the result is a new migration with these contents:
exports.up = function(knex) {
};
exports.down = function(knex) {
};
NOTE: I know that ´widget-knex-schema´is working because I use for other manually created migrations.
Can anyone link to any working examples or solutions on how to create migrations in run time?

Sequelize "model.associate" property doesn't work anymore?

I'm using sequelize v5.21 and sequelize-cli v5.5.1 for a MySQL database.
I'm currently trying to create some association 1:M under the following model generated code from the cli like so:
Where: a is the source and b the target.
model.associate = function (models) {
// associations can be defined here
a.hasOne(models.b);
}
and the other model, i have:
model.associate = function (models) {
// associations can be defined here
b.belongsTo(models.a);
}
Finally, im using this code to check the associations by creating a entries:
a.create({
...
}).then(a => {
return a.createB({
...,
}).catch(err => console.log(err));
}).catch(err => console.log(err)
);
In which i'm getting an error on the "a.createB()..." as is not a function...
So, i was curious enough and tried doing the associations right before checking the associations like so:
a.hasOne(b);
b.belongsTo(a);
In which works just fine...
My question is, is the "model.associate" property still working on the v5^?
Note: I have used the check associations method provided in the sequelize documentation but i prefer this one since it's easier to read.
I found nothing about model.associate in Sequelize documentation for v5, but in my project I'm using this code in main models directory:
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
where db is autogenerated by sequelize-cli models imported by sequelize['import']

Querying with Bookshelf

I recently started using Bookshelf. Examples in the documentation aren't very clear to me. This is one such example:
var knex = require('knex')({
client: 'mysql',
connection: process.env.MYSQL_DATABASE_CONNECTION
});
var bookshelf = require('bookshelf')(knex);
var User = bookshelf.Model.extend({
tableName: 'users',
posts: function() {
return this.hasMany(Posts);
}
});
var Posts = bookshelf.Model.extend({
tableName: 'messages',
tags: function() {
return this.belongsToMany(Tag);
}
});
var Tag = bookshelf.Model.extend({
tableName: 'tags'
})
User.where('id', 1).fetch({
withRelated: ['posts.tags']
}).then(function(user) {
console.log(user.related('posts').toJSON());
}).catch(function(err) {
console.error(err);
});
After the creation of three models (User, Posts, and Tag) - there is a query.
What exactly is the meaning of this query?
User.where('id', 1).fetch({withRelated: ['posts.tags']}).then(function(user) {
console.log(user.related('posts').toJSON());
}).catch(function(err) {
console.error(err);
});
What are withRelated, 'posts.tags', user.related('posts')? Can anyone tell me in simplest form, what those terms are and where they come from?
Finally, what is the meaning of the complete query?
It's all answered in the documentation, although not everything in the exact same place, so I can understand your confusion. You really have to read the whole thing to better understand it.
From the documentation of Model.fetch():
withRelated: Relations to be retrieved with Model instance. Either one or more relation names (...) A single property, or an array of properties can be specified as a value for the withRelated property.
From Collection.fetch():
The withRelated option may be specified to fetch the models of the collection, eager loading any specified relations named on the model.
From Collection.load():
... Nested eager loads can be specified by separating the nested relations with .
From Model.related():
The related method returns a specified relation loaded on the relations hash on the model, or calls the associated relation method and adds it to the relations hash if one exists and has not yet been loaded.
All of these are involved in the eager loading of relations on models.
What these methods do is load some data from a table that is related to the parent model's table in some way. In the example you posted, the User model has some Posts and the Posts themselves have some tags, so the relations go something like:
User
|_ Post
|_ Tag
These are expressed as methods on each model, for example, posts: function() { ... } in the User model.
When you specify a relation to eager load using withRelated, you use these method names. Furthermore you can eager load deeply nested relations by separating them with a ., and that's what you're seeing in the example.
So, putting everything together what that example is doing is searching for the User with id = 1 and also retrieving all the posts that belong to that user as well as all the tags that belong to all the posts that belong to the user.
Then to access these related objects you use the related('something') method of the model.

Mongoose: presave document with reference

What is the best practice for saving a document with a reference to another collection's document if the _id of that is not immediately available?
var ModelA = new Schema({
aUniqueIdentifer: String,
...
)};
ModelA's aUniqueIdentifier is provided from another datasource, and is used by other models to identify it.
var ModelB = new Schema({
aUniqueForeignKey: type String,
aRef : {
type: mongoose.Schema.Types.ObjectID,
ref: 'ModelA'
}
)};
So I might save a modelA: modelA = new ModelA({aUniqueIdentifer: '500'});
Then to save a mobdelB, I need to populate it's aRef with the ModelA object. What is the best practice to do so? Should I do a findOne(aUniqueForeignKey) to return the object before trying to save? This doesn't seem terribly efficient.
I looked into populate, but that seems to be for existing references.
You can use the .pre method to create a method that runs before saving and then put your logic inside that. It looks like this:
ModelB.pre('save', function(next) {
// Check if id is available
// if not run another method
// run next() to exit
next();
});
This will run before anytime you save ModelB.
Hope this helps, if you add some more information I might be able to provide a more specific solution.
You could try using populate.
Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s)
http://mongoosejs.com/docs/populate.html

Load includes on existing model

I'm trying to load includes on an existing model in sequelize. In express we pre check the models to see if they exist in the middleware.
So once we're in the actual "controller" we want to run some includes on that existing model that is passed in.
req.models.item.incude([
{model: Post, as: 'posts'}
])
Is there any way to accomplish this?
EDIT:
I know we can do something like this.
return req.models.item.getThing()
.then(function (thing) {
req.models.item.thing = thing;
return req.models.item;
});
But:
My expansions for includes are a dynamic property that come via url parameters, so they are not know ahead of time.
It I return the above you will not see the "thing" in the response. I need it nicely built as part of the original instance.
Something like a .with('thing', 'other.thing'); notation would be nice. Or in the case of sequelize .with({include: ...}); or .include([{model: ...}]);
If the variable req.models.item is already an Instance but without its other related instances ("includes"), then you could include them using something like the following code:
Item.findAll({
where: req.models.item.where(),
include: [{
model: SomeAssociateModel,
}]
})
.then(function(itemWithAssoc) {
// itemWithAssoc is an Instance for the same DB record as item, but with its associations
});
See here for some documentation. See here for a script demo'ing this.
Update: Given the instance, how do I just get the associated models?
To do this just use the automatically generated "getAssociation" getter functions, e.g.:
function find_associations_of_instance(instance) {
return instance.getDetails();
}
I've updated the script to include this as an example. For more information on these functions, see the SequelizeJS docs.

Categories

Resources