Meteor Collection - How to mark a property as unique - meteor

How can I mark a property in a Meteor Collection as unique? I am trying to create a document collection with each document having a name that is unique, and I can't figure out how to make this unique.

You know that mongo assigns a unique, non-human based _id to every document, and that often the title a user decides to call something doesn't need to be unique. That being said,
http://docs.mongodb.org/manual/tutorial/create-a-unique-index/ tells you how to make an index that forces a field to be unique:
db.collection.ensureIndex( { a: 1 }, { unique: true } );
You can run that from the mongo shell. It can also be called from the server js only. If you've created a collection by
Diaries = new Meteor.collection();
then just after adding the collection, in server code, you could add
Diaries.ensureIndex({ title: 1},{ unique: true });

Related

Create item if not found and return old one if existing in dynamodb, in one call

In DynamoDb, is it possible to do a conditional put and return the old item if there already was a matching item?
I would like something like the following to create the row if the user does not already exist, and if it does exist, I want it to return the old item (with whatever name was there before). I.e., insert item if new, else just read existing item.
await documentClient.put({
TableName: 'table',
Item: {
userId: 'user0',
name: 'smith',
},
ConditionExpression: 'attribute_not_exists(userId)',
});
Is this possible to do in one call?
Sort of.
If you use UpdateItem it will create or update the item. It only updates the fields that have changed however, so if your fields have not changed, then no update will be made. It will also create the item if you do not have it.
Using Conditional Items for this will return an ErrorCode if the condition fails ConditionalCheckFailedException - which would require an additional operation.
(I don't use the javascript SDK that much, more in python, so I'm not sure this is the correct version/page but here is some documentation:
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#updateItem-property )

How to only allow a unique document ID to be upload to Firebase Firestore using Rules [duplicate]

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.

Firestore: How to query a map of roles inside a subcollection of documents?

I have the following subcollection structure:
/projects/{projectId}/documents/{documentId}
Where each document has an access map used for checking security rules for each document:
{
title: "A Great Story",
content: "Once upon a time ...",
access: {
alice: true,
bob: false,
david: true,
jane: false
// ...
}
}
How the security rules are set:
match /{path=**}/documents/{documentId} {
allow list, get: if resource.data.access[request.auth.uid] == true;
}
When I want to query all documents alice can access across all projects:
db.collectionGroup("documents").where(`access.${uid}`, "==", true);
I get an error telling me that I need to create a collection group index for access.alice, access.david, etc.
How can I overcome this indexing issue with collection group queries?
As a quick overview of indexes, there are two types - a composite index (used for connecting multiple fields together) or a single-field index. By default, single-field indexes are automatically created for each field of a document for ordinary collection queries and disabled for collection group queries. If a field's value is an array, the values of that array will be added to an Array Contains index. If a field's value is primitive (string, number, Timestamp, etc), the document will be added to the Ascending and Descending indexes for that field.
To enable querying of the access field for a collection group query, you must create the index. While you could click the link in the error message you get when making the query, this would only add an index for the user you were querying (e.g. access.alice, access.bob, and so on). Instead, you should use the Firebase Console to tell Cloud Firestore to create the index for access (which will create indexes for each of its children). Once you've got the console open, use the "Add Exemption" button (consider this use of "exemption" to mean "override default settings") to define a new indexing rule:
In the first dialog, use these settings:
Collection ID: "documents"
Field Path: "access"
Collection: Checked ✔
Collection group: Checked ✔
In the second dialog, use these settings:
Collection Scope
Collection Group Scope
Ascending
Enabled
Enabled
Descending
Enabled
Enabled
Array Contains
Enabled
Enabled
In your security rules, you should also check if the target user is in the access map before doing the equality. While accessing a missing property throws a "Property is undefined on object." error which denies access, this will become a problem if you later combine statements together with ||.
To fix this, you can either use:
match /{path=**}/documents/{documentId} {
allow list, get: if request.auth.uid in resource.data.access
&& resource.data.access[request.auth.uid] == true;
}
or provide a fallback value when the desired key is not found:
match /{path=**}/documents/{documentId} {
allow list, get: if resource.data.access.get(request.auth.uid, false) == true;
}
As an example of the rules breaking, let's say you wanted a "staff" user to be able to read a document, even if they aren't in that document's access map.
These rules would always error-out and fail (if the staff member's user ID wasn't in the access map):
match /{path=**}/documents/{documentId} {
allow list, get: if resource.data.access[request.auth.uid] == true
|| get(/databases/$(database)/documents/users/$(request.auth.uid)).data.get("staff", false) == true;
}
whereas, these rules would work:
match /{path=**}/documents/{documentId} {
allow list, get: if resource.data.access.get(request.auth.uid, false) == true
|| get(/databases/$(database)/documents/users/$(request.auth.uid)).data.get("staff", false) == true;
}
As a side note, unless you need to query for documents where a user does not have access to it, it is simpler to simply omit that user from the access map.
{
title: "A Great Story",
content: "Once upon a time ...",
access: {
alice: true,
david: true,
// ...
}
}
As noted on https://cloud.google.com/firestore/docs/query-data/queries#collection-group-query "Before using a collection group query, you must create an index that supports your collection group query. You can create an index through an error message, the console, or the Firebase CLI."
In this case you'll want to enable CollectionGroup queries on the access map (https://cloud.google.com/firestore/docs/query-data/indexing#add_a_single-field_index_exemption)
You would have to create an index for it as mentioned by #Jim.
If you don't want to get much into it, you can just catch the error and it'll have a direct link to to create one as shown below:
db.collectionGroup("documents").where(`access.${uid}`, "==", true).get().then((snap) => {
//
}).catch((e) => console.log(e))
Other ways as mentioned in the documentation include:
Before using a collection group query, you must create an index that
supports your collection group query. You can create an index through
an error message, the console, or the Firebase CLI.

Firestore create documents if they don't exist, skip if they do

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.

Meteor update a Collection object

in Meteor, I'm having a collection with a Schema, and a number of items are added dynamically.
In this case, I'm dealing with milestones object, and once the user check one off I want to update complete in this Collections item to true (default is false)
Here is my schema
milestones: {
type: Array,
optional: true
},
'milestones.$': {
type: Object
},
'milestones.$.name': {
type: String
},
'milestones.$.hours': {
type: Number
},
'milestones.$.complete': {
type: Boolean
}
How do I write a $set statement for this?
You have an array of objects so, $elemMatch do the trick here.
Projects.update({_id:this._id},{milestones:{$elemMatch:{'milestones.$‌​.name':this.name}},{$set:{'milestone.$.complete':value}}})
So thanks to Aldeed I found a solution - which needs to be called on server side, otherwise it won't let the update happen. Do:
Projects.update({_id:currentPostId, 'milestones.name':name}, {$set:{'milestones.$.complete':true}});
The function is called on the client with Meteor.call with all needed params.
According to your schema you have an object containing an array of objects. So you should write you $set like this:
{$set: {'milestone.$.complete':value}}
This will update the first array element corresponding to the query.
You can find here the official documentation if you want to know more about arrays updates in Mongo.

Resources