Is it possible to apply a modifier to a document and see the result before doing the actual update query?
Something like a virtual/simulated update.
My purpose is to validate any possible update inside a before hook through an external service that only accepts some final, post-operation document and then possibly abort the operation.
Minimongo has a function that can be used: LocalCollection._modify(doc, modifier). In order to load LocalCollection on the server, add minimongo to local/packages
write your logic for logic inside a allow,deny function
update(userId, doc, fieldNames, modifier)
fieldNames is an array of the (top-level) fields in doc that the
client wants to modify, for example ['name', 'score'].
modifier is the raw Mongo modifier that the client wants to execute;
for example, {$set: {'name.first': "Alice"}, $inc: {score: 1}}.
Posts.allow({
update: function (userId, doc, fields, modifier) {
//you can return false here, if you dont want to update db
},
});
Related
I want to create Firestore documents if they don't exist - if they do exist, skip them (don't update).
Here's the flow
var arrayOfRandomIds = [array of 500 random numbers];
for (var id of arrayOfRandomIds)
{
var ref = db.collection("tickets").doc(id);
batch.set(ref, {name: "My name", location: "Somewhere"}, { merge: true });
}
batch.commit();
I just want to know, would this overwrite any existing documents if they exist? I don't want anything overwritten, just skipped.
Thanks.
I think you can use security rules to accomplish that. That way you won't be charged for an additional document read to see if it already exists.
service cloud.firestore {
match /databases/{database}/documents {
match /tickets/{id} {
allow create;
}
}
}
Meanwhile there is a "create but don't overwrite" function.
Assuming you are using JavaScript here is the reference: https://googleapis.dev/nodejs/firestore/latest/DocumentReference.html#create
Here is the corresponding example code from the docs:
let documentRef = firestore.collection('col').doc();
documentRef.create({foo: 'bar'}).then((res) => {
console.log(`Document created at ${res.updateTime}`);
}).catch((err) => {
console.log(`Failed to create document: ${err}`);
});
Using .create() instead of .set() should do the trick for you without relying on security rules for application logic.
Firestore doesn't have a native "create but don't overwrite" operation. Here are the only available operations:
update: only change the contents of an existing document
set without merge: create or overwrite
set with merge: create or update if exists
Instead of a batch, what you can do instead is perform a transaction that checks to see if the document exists, then creates it conditionally if it does not already exist. You will have to write that logic inside your transaction handler.
I want to create Firestore documents if they don't exist - if they do exist, skip them (don't update).
In that case, you should check if a particular document actually exists in a collection, right before the write operation takes place. If it does not exist, create it, otherwise take no action.
So you should simply use set() function, without passing merge: true.
I want to create Firestore documents if they don't exist - if they do exist, skip them (don't update).
Here's the flow
var arrayOfRandomIds = [array of 500 random numbers];
for (var id of arrayOfRandomIds)
{
var ref = db.collection("tickets").doc(id);
batch.set(ref, {name: "My name", location: "Somewhere"}, { merge: true });
}
batch.commit();
I just want to know, would this overwrite any existing documents if they exist? I don't want anything overwritten, just skipped.
Thanks.
I think you can use security rules to accomplish that. That way you won't be charged for an additional document read to see if it already exists.
service cloud.firestore {
match /databases/{database}/documents {
match /tickets/{id} {
allow create;
}
}
}
Meanwhile there is a "create but don't overwrite" function.
Assuming you are using JavaScript here is the reference: https://googleapis.dev/nodejs/firestore/latest/DocumentReference.html#create
Here is the corresponding example code from the docs:
let documentRef = firestore.collection('col').doc();
documentRef.create({foo: 'bar'}).then((res) => {
console.log(`Document created at ${res.updateTime}`);
}).catch((err) => {
console.log(`Failed to create document: ${err}`);
});
Using .create() instead of .set() should do the trick for you without relying on security rules for application logic.
Firestore doesn't have a native "create but don't overwrite" operation. Here are the only available operations:
update: only change the contents of an existing document
set without merge: create or overwrite
set with merge: create or update if exists
Instead of a batch, what you can do instead is perform a transaction that checks to see if the document exists, then creates it conditionally if it does not already exist. You will have to write that logic inside your transaction handler.
I want to create Firestore documents if they don't exist - if they do exist, skip them (don't update).
In that case, you should check if a particular document actually exists in a collection, right before the write operation takes place. If it does not exist, create it, otherwise take no action.
So you should simply use set() function, without passing merge: true.
This Meteor code is working fine, but I would like to ask if it is the way Meteor does things or it is a un predictable side effect that may change under some condition later.
The things is that when I do
DisplayCol.insert({action: 'task1', element: 'p', value: value_variable});
Meteor also inserts the correct userId (using 2 different browsers logged in as 2 different users) which I did not explicitly included in the document.
The above line of code is inside a server side function which is called from Meteor method.
here is the relevant information;
//lib/collection.js
DisplayCol = new Mongo.Collection('displayCol');
//server.js
Meteor.publish('displayCol', function () {
return DisplayCol.find({userId: this.userId});
});
DisplayCol.before.insert(function (userId, doc) {
doc.userId = userId;
});
In the docs of Collection hooks > Additional notes > second bulleted paragraph says:
userId is available to find and findOne queries that were invoked within a publish function.
But this is a collection.insert. So should I explicitly include the userId in the document or let the collection hook do its hidden magic? Thanks
No, there is no hidden magic in that code, your before hook is inserting the userId field in the document.
When you do an insert like this,
DisplayCol.insert({action: 'task1', element: 'p', value: value_variable});
the doc that your are inserting is { action: 'task1', element: 'p', value: value_variable }
Because, you have this hook,
DisplayCol.before.insert(function (userId, doc) {
doc.userId = userId;
});
it changes the doc before inserting into collection. So the above hook will change your doc to {action: 'task1', element: 'p', value: value_variable, userId: 'actual-user-id' }
This is the expected behaviour.
Regarding your other point in the question,
userId is available to find and findOne queries that were invoked
within a publish function.
Previously userId parameter in the find and findOne returns null, so user needs to pass userId as a parameter as mentioned in this comment. Additional notes mentions that the hack is not required any more. It has nothing to do with inserting userId field into the collection document.
To have a quick test, remove the DisplayCol.before.insert hook above, you will not see userId field in the newly inserted documents.
UPDATE
Just to clarify your doubt further, from the 4th point in the docs that you provided
It is quite normal for userId to sometimes be unavailable to hook
callbacks in some circumstances. For example, if an update is fired
from the server with no user context, the server certainly won't be
able to provide any particular userId.
which means that if the document is inserted or updated on the server, there will be no user associated with the server, in that case, userId will return null.
Also you can check the source code yourself here. Check the CollectionHooks.getUserId method, it uses Meteor.userId() to get the userId.
CollectionHooks.getUserId = function getUserId() {
var userId;
if (Meteor.isClient) {
Tracker.nonreactive(function () {
userId = Meteor.userId && Meteor.userId(); // <------- It uses Meteor.userId() to get the current user's id
});
}
if (Meteor.isServer) {
try {
// Will throw an error unless within method call.
// Attempt to recover gracefully by catching:
userId = Meteor.userId && Meteor.userId(); // <------- It uses Meteor.userId() to get the current user's id
} catch (e) {}
if (!userId) {
// Get the userId if we are in a publish function.
userId = publishUserId.get();
}
}
return userId;
};
I am using meteor. I would like to permit update to objects in picks collection only via call calls from client to server and not via the usual DDP api.
In that way I can control the exact update function in the server and do there some complex permissions check for each update.
Is the check Meteor.isServer good for this case, or is it not doing what I think?
Picks = new Mongo.Collection("picks");
Picks.allow({
insert: function (userId, pick) {
return userId && pick.owner === userId;
},
update: function (userId, pick, fields, modifier) {
return Meteor.isServer;
},
remove: function (userId, pick) {
if (userId !== pick.owner)
return false;
return true;
}
});
Allow and deny rules only apply to updates from a client, which makes things much simpler than you're imagining.
If I understand what you're trying to do correctly, all you need to do is return false from all of the Picks.allow functions, so that no client writes will be accepted. This won't do anything to prevent you writing to the collection from the server side, so provided you have appropriate validation in your Meteor.methods, you're free to do whatever you want to the collection within them as they're server-side code, and just have the user invoke updates via Meteor.call.
I am using meteor and i am a bit confused about the relationship between publishing/subscribing to documents and querying/returning collections to a client using the handlebars #each items helper.
I understand that by publishing and subscribing to certain documents, i get the reactive updating on the client side browser when things change.
I find my self writing very complex (role oriented) publish functions and writing the equivalent to return items to the client. For example,
Meteor.publish("directory", function () {
var user = Meteor.users.findOne({_id:this.userId});
//role and logic left out on purpose
return Meteor.users.find({}, {fields:{emails:1, profile:1}});
});
and the subscribe
if (Meteor.userId() != null) {
Meteor.subscribe("directory");
}
Template is called show people and the helper 'users'
Template.show_people.users = function () {
users = Meteor.users.find({}).fetch();
return users;
};
My question is, are things supposed to be done this way?. Do we return our list helpers the same query we used for publish?
You can give a query cursor to the #each Handlebars function. In fact, it's recommended. In this manner, there will be a smart update of the DOM: when a document is added to the Cursor, Handlebars will only create new DOM nodes for that document, and not recreate the DOM nodes for the documents that were already present. This is not the case when you provide an array.
So that third piece of code can just be:
Template.show_people.users = function () {
return Meteor.users.find({});
};
Note also that a collection.find() done client-side will only look in the documents inside your miniMongo storage... you're not doing a search through the entire server database, but only through the documents that the server has published to you.
So that complex, role-oriented logic is only necessary inside your Meteor.publish() function.