Initializing Some Collections When A User Creates An Account

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?

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.

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.

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:

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.

1 Like

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;
});
1 Like

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

@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);
});
1 Like

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.

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.

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.

1 Like

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

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.

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: