27

I am trying to create a two column unique index on the underlying mongodb in a meteor app and having trouble. I can't find anything in the meteor docs. I have tried from the chrome console. I have tried from term and even tried to point mongod at the /db/ dir inside .meteor . I have tried

Collection.ensureIndex({first_id: 1, another_id: 1}, {unique: true}); variations.

I want to be able to prevent duplicate entries on a meteor app mongo collection.

Wondering if anyone has figured this out?

I answered my own question, what a noob.

I figured it out.

  1. Start meteor server

  2. Open 2nd terminal and type meteor mongo

Then create your index...for example I did these for records of thumbsup and thumbsdown type system.

db.thumbsup.ensureIndex({item_id: 1, user_id: 1}, {unique: true})
db.thumbsdown.ensureIndex({item_id: 1, user_id: 1}, {unique: true})

Now, just gotta figure out a bootstrap install setup that creates these when pushed to prod instead of manually.

Steeve Cannon
  • 3,682
  • 3
  • 36
  • 49
  • 1
    I posted my answer before I saw your edit, but note that by only enabling indexing on the server-side your users will think they're giving multiple upvotes even if those aren't actually being saved. So probably best to also query before inserting as a workaround for that. – danny Apr 16 '12 at 03:26
  • 4
    Would be better if you post your own answer separately from the question. Is more legible. – zVictor Nov 06 '12 at 19:06

4 Answers4

31

Collection._ensureIndex(index, options)

Searching inside Meteor source code, I found a bind to ensureIndex called _ensureIndex. For single-key basic indexes you can follow the example of packages/accounts-base/accounts_server.js that forces unique usernames on Meteor:

Meteor.users._ensureIndex('username', {unique: 1, sparse: 1});

For multi-key "compound" indexes:

Collection._ensureIndex({first_id:1, another_id:1}, {unique: 1});

The previous code, when placed on the server side, ensures that indexes are set.

Warning

Notice _ensureIndex implementation warning:

We'll actually design an index API later. For now, we just pass through to Mongo's, but make it synchronous.

zVictor
  • 3,610
  • 3
  • 41
  • 56
15

According to the docs "Minimongo currently doesn't have indexes. This will come soon." And looking at the methods available on a Collection, there's no ensureIndex.

You can run meteor mongo for a mongo shell and enable the indexes server-side, but the Collection object still won't know about them. So the app will let you add multiple instances to the Collection cache, while on the server-side the additional inserts will fail silently (errors get written to the output). When you do a hard page refresh, the app will re-sync with server

So your best bet for now is probably to do something like:

var count = MyCollection.find({first_id: 'foo', another_id: 'bar'}).count()
if (count === 0)
    MyCollection.insert({first_id: 'foo', another_id: 'bar'});

Which is obviously not ideal, but works ok. You could also enable indexing in mongodb on the server, so even in the case of a race condition you won't actually get duplicate records.

danny
  • 10,103
  • 10
  • 50
  • 57
  • Thank you @danny I was just noticing that behavior as this post came in. – Steeve Cannon Apr 16 '12 at 03:59
  • BTW, for anyone wondering, I implemented both. The above logic on the front end and a unique index on the back end for safety and sanity sake. – Steeve Cannon Apr 21 '12 at 13:30
  • 3
    this workaround still doesn't seem ideal.. what if you call `MyCollection.find` before the app has pulled all the data from the server? – Lloyd Aug 11 '12 at 14:54
  • Another pattern is to do inserts with a Meteor Method. Something like: Meteor.call('addAFoo', fooData, function() {}); – Michael Cole Mar 13 '16 at 16:34
3

The Smartpackage aldeed:collection2 supports unique indices, as well as schema-validation. Validation will both occure on server and client (reactivly), so you can react on errors on the client.

macrozone
  • 850
  • 1
  • 7
  • 21
1

Actually why not use upsert on the server with a Meteor.method and you could also send also track it with a ts: // Server Only

Meteor.methods({
 add_only_once = function(id1,id2){
   SomeCollection.update(
     {first_id:id1,another_id:id2},{$set:{ts:Date.now()}},{upsert:True});
 }
});

// Client

Meteor.call('add_only_once',doc1._id, doc2._id);

// actual code running on server

if(Meteor.is_server) {
    Meteor.methods({
        register_code: function (key,monitor) {
             Codes.update({key:key},{$set:{ts:Date.now()}},{upsert:true});
        }
     ...
limeyd
  • 210
  • 1
  • 3
  • 1
    According to docs.meteor.com upsert is not supported yet with Mongo/MiniMongo stuff. Otherwise that would be good. – Steeve Cannon Jun 07 '12 at 19:01
  • upsert working? very good news that code works now maybe latest releases of Meteor fixed this? I would not know, haven't used Meteor in a couple of weeks. – Steeve Cannon Jun 11 '12 at 21:51
  • 1
    I had no idea the `upsert` option was implemented on the server `Collection.update` call.. this finally gives me a cast-iron way to add a User to the Mongo table ONLY if it doesn't exist.. – Lloyd Aug 11 '12 at 15:17
  • First, upserts aren't supported as of 12 Aug 2013. Second, you could put anything into that 3rd set of curly brace, it just gets ignored (just tested it on 064) – Stephan Tual Aug 12 '13 at 20:19
  • upserts are available since 0.6.6 – Rebolon Oct 14 '13 at 13:43