Initializing Some Collections When A User Creates An Account


#1

I am really struggling with what is probably a very simple concept. When a new user is created, I want to initialize a user’s account with a bunch of rows (documents) in some database collections.

I can’t for the life of me figure out where that code should go. I have tried placing it in a helper which is called by the template that would use those records, by doing:

result = MyCollection.find(...).count();
if(count == 0) {
  Meteor.call('addRecords', ...);
} else {
  return MyCollection.find({});
}

But because of the reactivity it always returns a 0 on the first pass and so I always insert new records. In other words, I believe the way I am doing it is not idempotent.

I don’t think putting it in a helper is the right place, but I have no clue where else to put it. I’m betting there is some magic reactive event fired when someone first creates a new account and logs in for the first time?

If I want to create a bunch of records one time, when a user first creates their account using the accounts-password function, where should I place that code?


#2

Ugh, I just found this at docs.meteor.com: http://docs.meteor.com/#/full/accounts_oncreateuser

So I have some light bathroom reading, and then I think I will be set.


#3

OK my first test is a fail. I tried this:

if (Meteor.isClient) {
    Accounts.ui.config({
        passwordSignupFields: "USERNAME_ONLY"
    });
    Accounts.onCreateUser(function(options, user) {
        console.log('New account created!');
    });
}

Signed up a new user and got:

TypeError: Accounts.onCreateUser is not a function
Accounts.onCreateUser(function(options, user) {

So I have no clue how to use this function.


#4

Whoa. Place it in isServer. Working! Now I know to look in the upper right on the documentation for where to place stuff! :slight_smile:


#5

Bah, this doesn’t help me at all. I can only manipulate the user object before it’s added to the database. How do I add collection records after the user is added to the database?

And if anybody wants to explain to my pea-brain why find().count() returns 0 the first time it’s called and then is called again and returns the correct number of rows, that would be super cool. I am sure it has something to do with reactivity, but I can’t wrap my brain around it.


#6

I think you are on the right track, I am doing something similar you can do something like:

Accounts.onCreateUser(function(options, user) {
    if(!options || !user) {
      console.log('error creating user');
    return;
  }

  // add default exercises
  Meteor.settings.defaultExercises.forEach(function(exercise) {
      Exercises.insert({
          name: exercise.name,
          description: exercise.description,
          userId: user._id
      });
  });

  // add more stuff to other collections
  // ...
  
  return user;
});

#7

Ahhh, yeah, that looks promising. So we have the unique userid before it’s submitted. Nice.


#8

@riebeekn are you sure about this? The user object does not have an _id within the onCreateUser block and it gets one only after the user is actually successfully created.

So your code should actually be:

Accounts.onCreateUser(function(options, user) {
    if(!options || !user) {
      console.log('error creating user');
    return;
  }
  // make sure the user actually gets an ID
  // _makeNewID() is an undocumented feature and may change in the future
  user._id = Meteor.users._makeNewID();

  // add default exercises
  Meteor.settings.defaultExercises.forEach(function(exercise) {
      Exercises.insert({
          name: exercise.name,
          description: exercise.description,
          userId: user._id
      });
  });

  // add more stuff to other collections
  // ...
  
  return user;
});

@alfreema there are two other possible ways that I’ve used before to initialize user related persisted data upon user creation. One is from @awatson1978’s meteor-cookbook

Meteor.users.find({ initialized: { $exists: false } }, { fields: { _id: 1 } }).observe({
  added: function (user) {
    // insert default user docs here if not already present
   Meteor.users.update(user._id, { $set: {initialized: true} });
  }
});

And another is using the collection-hooks package

Meteor.users.after.insert(function (userId, doc) {
  // the currently logged in user if there is any (might be different than the new user
  console.log(userId);

  // the new user's id
  console.log(this._id);

  // the same
  console.log(doc._id);

  // the user document itself
  console.log(doc);
});

#9

It’s seems the user id is available, when I add

...
...
// add default exercises
  console.log(user._id);
...
...

I see the id value and looking at my documents in Robomongo everything seems good. I haven’t messed with this code for awhile but I don’t recall / see anything funky I’m doing to get the id within the onCreateUser block.


#10

Hmm this is interesting. Perhaps this hack has become redundant and I’m glad it has.

On a side note, onCreateUser() may fail so mutating the database based on this might not be a good idea. See this:

I suggest you use one of the other two methods I’ve given in my previous comment. They run after actual user creation so they are safe.


#11

Seems to me that both techniques have their place. riebeekn’s technique would be good for log tables, while serkandurusoy’s techniques are better where orphaned records are undesirable (the general situation).

I like the cleanliness/maintainability of the collection-hooks example. I think I’ll go that route for now.


#12

OK the mtb33:collection-hooks package rocks. So easy to use and the resulting code looks great!


#13

Ugh. This has confused people on StackOverflow too - http://stackoverflow.com/questions/12984637/is-there-a-post-createuser-hook-in-meteor-when-using-accounts-ui-package

I’ve submitted a PR for the docs to mention that _id is available.


#14

Yep, there is some confusion there. And I think having an _id there does not help either. It further strengthens the misconception that onCreateUser is a post hook, which (un)clearly is not.

I believe a better fix would be not making _id available at all :smile: