Why doesn't enabling timestamps work with Mongoose's set option? - mongodb

I'm diligently trying not to have a conniption while dealing with this issue. I'm simply trying to add timestamps to new and existing documents by using set() per Mongoose's documentation here, which looks like this:
new Schema({..}, options);
// or
const schema = new Schema({..});
schema.set(option, value); // <----- THIS PART
My code:
Object.entries(sortedSchemas.lore).forEach(([name, schema]) => {
schema.set("timestamps", true).add(contentLock(schema));
console.log(name + ":", { schema }); // FOR TESTING
});
Using add() works fine (where I'm using contentLock(schema)) as far as adding fields to all the schema in sortedSchema.lore. And when I console log the modified schema it shows that the timestamps option is true. And yet, when I create a new document or update one, I don't see createdAt or updatedAt. Why is that?
Also, if I manually set timestamps: true directly in the options parameter of each schema it seems to work almost as expected (createdAt seems to disappear after updating a document, but that's a separate issue).
Am I misunderstanding set()?
Any help with this is much appreciated.

Related

Prisma not performing update on record

There are a few things going on with Prisma update that I just don't get.
Why is the update (using the ORM way) not performed ?
Why the value of data.address seems to affect the outcome of the update ?
Do I have to provide all of the fields of the entity when updating ? Or in this case, could I just put what I want changed inside of data ?
I am using #prisma/client#3.15.2
Here is what I am currently working with:
const { valid: validFor, expire, state, address, ...safeProperties } = data;
const addressAsUnsigned = address >>> 0; // address is an ip address represented as an integer. It needs to be treated as unsigned
const extendBy = newValidFor - validFor;
const extended = add(expire, { seconds: extendBy });
const payload: Prisma.DataTableUpdateArgs = {
where: { address: addressAsUnsigned },
data: {
...safeProperties,
address: addressAsUnsigned,
expire: extended,
valid: authenticated,
state: {},
},
}
Logger.debug(payload);
// contains the changes I expect
const result = await db.dataTable.update(payload);
Logger.debug(result);
// result contains the same values as before the update.
// And indeed, when I check the database, nothing changed.
// Something like this does what I want, so there is really nothing complicated going on...
await db.$executeRaw`
UPDATE data_table SET
expire = ${extended},
valid = ${authenticated}
WHERE address = ${addressAsUnsigned}
`;
Hopefully, I have not missed something too obvious.
In my experience,
Why is the update (using the ORM way) not performed ?
You might be updating the wrong thing. Is your address an #unique field in your prisma.schema?
Why the value of data.address seems to affect the outcome of the update ?
Prisma might have messed some things up with wrong data. If your data is not unique, you might be updating the first row with that address. If you want to update multiple fields with same address, use updateMany
Do I have to provide all of the fields of the entity when updating ? Or in this case, could I just put what I want changed inside of data ?
No, you only need to put in the data that you need. In your "where" field, add the unique address, and in your data, only the fields that you are changing. In your case, expired and valid. If you want to skip updating some values, use "expired: undefined" and so on
Since you are using typescript, I would advise you to put your object directly inside the prisma update to get the correct types. (At least to fix this problem)
prisma.dataTable.update({where: {...}})
This way you will get the correct types. There is also a command to list all available args inside (control + space on mac)
Note that using the spread operator (...) will remove the listed types, so use it last.
Some other things: double check if your prisma import is correct. Is your data correct, is your returned data correct? Did you refresh your database on update? It might be updated but you just need to refresh for new changes.

MongoDB findOneAndReplace log if added as new document or replaced

I'm using mongo's findOneAndReplace() with upsert = true and returnNewDocument = true
as basically a way to not insert duplicate. But I want to get the _id of the new inserted document (or the old existing document) to be passed to a background processing task.
BUT I also want to log if the document was Added-As-New or if a Replacement took place.
I can't see any way to use findOneAndReplace() with these parameters and answer that question.
The only think I can think of is to find, and insert in two different requests which seems a bit counter-productive.
ps. I'm actually using pymongo's find_one_and_replace() but it seems identical to the JS mongo function.
EDIT: edited for clarification.
Is it not possible to use replace_one function ? In java I am able to use repalceOne which returns UpdateResult. That has method for finding if documented updated or not. I see repalce_one in pymongo and it should behave same. Here is doc PyMongo Doc Look for replace_one
The way I'm going to implement it for now (in python):
import pymongo
def find_one_and_replace_log(collection, find_query,
document_data,
log={}):
''' behaves like find_one_or_replace(upsert=True,
return_document=pymongo.ReturnDocument.AFTER)
'''
is_new = False
document = collection.find_one(find_query)
if not document:
# document didn't exist
# log as NEW
is_new = True
new_or_replaced_document = collection.find_one_and_replace(
find_query,
document_data,
upsert=True,
return_document=pymongo.ReturnDocument.AFTER
)
log['new_document'] = is_new
return new_or_replaced_document

Mongoose - Querying a collection for a new property with a default value without having to resave every element

We recently added a new property to one of our Mongoose schemas that defines takes a String with an enum validator and a default value. We now need to query for documents using that property but it isn't set for pre-existing documents until after the query happens. Is there any way to get around this without having to re-save every document in that collection that existed before this change, or if not is there a best practice for how to do that cleanly?
The new property:
sales_category: {
type: String,
required: true,
enum: ["Prospect", "Subscriber", "Activated Trial", "Expired Subscriber", "Expired Free Trial"],
default: "Prospect"
}
The query:
Account.find({sales_category: "Prospect"}).populate("account_holder").exec(function(err, accounts) {
Edit: I just found https://stackoverflow.com/a/14288276/8324 which seems to imply that there is no clean way to do this, the suggestion to leave it as it is and just invert any query for {sales_category: "Prospect"} to a query for not any of the other categories seems like the best solution provided the enum never changes. I'm not sure if we can guarantee that it in this use case though so I think I might end up falling back to the "re-save everything" solution, even if it doesn't feel great.
I'll leave this open for now in case someone has a better solution.
What about using an OR conditional with an $exists operator?
Account.find().or([{sales_category: "Prospect"}, { sales_category: { $exists: false }}]).populate("account_holder").exec(function(err, accounts) {...});
One note: this doesn't use an index which would only be a concern if this is used on a large collection.

Meteor - Cannot Access All Read Operations

I'm new to Meteor. I've been stuck on this problem for a while. I can successfully adds items to a collection and look at them fully in the console. However, I cannot access all of the read operations in my .js file.
That is, I can use .find() and .findOne() with empty parameters. But when I try to add .sort or an argument I get an error telling me the object is undefined.
Autopublish is turned on, so I'm not sure what the problem is. These calls are being made directly in the client.
This returns something--
Template.showcards.events({
"click .play-card": function () {
alert(Rounds.find());
}
})
And this returns nothing--
Template.showcards.events({
"click .play-card": function () {
alert(Rounds.find().sort({player1: -1}));
}
})
Sorry for the newbie question. Thanks in advance.
Meteor's collection API works a bit differently from the mongo shell's API, which is understandably confusing for new users. You'll need to do this:
Template.showcards.events({
'click .play-card': function() {
var sortedCards = Rounds.find({}, {sort: {player1: -1}}).fetch();
console.log(sortedCards);
}
});
See this for more details. Also note that logging a cursor (the result of a find) probably isn't what you want. If you want to see the contents of the documents, you need to fetch them.
Rounds.find().sort({player1: -1}) returns a cursor, so you will want to do this:
Rounds.find().sort({player1: -1}).fetch();
Note that this returns an Array of document objects. So you would do something more like this:
docs = Rounds.find().sort({player1: -1}).fetch();
alert(docs[0]);

Defining a Mongoose Schema on the fly

I have a model file that gathers together all my Mongoose models. One of the models I would like to initialize with a variable number of fields. Currently I'm defining more fields than I think I am going to require:
TallySchema = new mongoose.Schema
0: Number
1: Number
...
20: Number
Obviously this is not ideal. I see Mongoose will let you specify options outside the Schema definition but can't see how to add new fields (or paths, I guess, in Mongoose).
Based on the mongoose plugin documentation it looks like you can just do:
schema.add({ field: Number })
This would need to be verified, but looking at the source it should be possible:
In the Schema constructor, it simply passes the definition object to this.add() (source).
The actual paths then get created within Schema.prototype.add (source).
So, seems like all you would need to do is something like:
// not sure what this looks like in CoffeeScript
TallySchema.add({ /* new property definition here */ });
I found this in the Mongoose documentation page:
var ToySchema = new Schema;
ToySchema.add({ name: 'string', color: 'string', price: 'number' });
You can use the 'mixed' type to encapsulate your values. It wouldn't be possible to have them be at the top level though but it works great otherwise.
new mongoose.Schema({
average: Number,
countPerRating: mongoose.Schema.Types.Mixed,
});
This is an excerpt from a mapreduce schema. I use the mixed type to store the number of times someone gives a certain rating, so we can say things like "10 1 star ratings, 45 4 star ratings", etc.
mixed type worked great for that.