Autoform + Accounts.createUser()


#1

Hello, something in my brain is not clicking when it comes to creating a new user with Autoform. I’m hoping someone can help make it click.

First, the packages used by my app (via meteor list):

accounts-password   1.1.1
accounts-ui         1.1.5
aldeed:autoform     5.3.2
aldeed:collection2  2.3.3
iron:router         1.0.7
meteor-platform     1.2.2
twbs:bootstrap      3.3.4

Second, my schema in /lib/collections.js

Schema.UserProfile = new SimpleSchema({
    firstName: {
        type: String,
        regEx: /^[a-zA-Z-]{2,25}$/,
        optional: false
    },
    lastName: {
        type: String,
        regEx: /^[a-zA-Z]{2,25}$/,
        optional: false
    }
});

Schema.User = new SimpleSchema({
    username: {
        type: String,
        regEx: /^[a-z0-9A-Z_]{3,15}$/
    },
    emails: {
        type: [Object],
        optional: false
    },
    "emails.$.address": {
        type: String,
        regEx: SimpleSchema.RegEx.Email
    },
    "emails.$.verified": {
        type: Boolean
    },
    profile: {
        type: Schema.UserProfile,
        optional: false
    },
    status: {
    	type: String,
    	optional: false
    }
});

Meteor.users.attachSchema(Schema.User);

So, it appears Accounts.createUser() only accepts username, email, password, and profile. The problem is, I need to also include status, as described in the schema above. What’s a Meteor newbie to do? Should I include status in profile? According to the Meteor docs, that isn’t encouraged: “do not store anything on profile that you wouldn’t want the user to edit”.

Please enlighten me :slight_smile:


#2

Wrap it all in a method and make it a two step process and post the autoform to a method.

Pseudo code:

Meteor.methods({
  'createUser' : function( /* complete info to create a user */) {
    var newUser = Accounts.createUser(/* standard args */);
    Meteor.users.update(newUser, /* set the extra information */);
  }
});

#3

Thanks. It appears Autoform is complaining after the Accounts.createUser call because status is a required field: Error: Status is required It needs to be required so I’m not sure how to work around this situation.


#4

Hello

I’m pretty sure the best way to do it is by using Accounts.onCreateUser.

Accounts.onCreateUser(function (options, user) {

    // initialize the user object

    user.status = null;
    check(user, Schema.User);

    return user;
});

With this function, you can modify/update the user object before it is inserted in the database. Unless Accounts.validateNewUser, this function is really intended to modify the user object since you have to return it, and this is what will be inserted in DB.

Ass you can see, i am also useing check with your schema to ensure that the returned user object is valid, unless it will throw an error.

This code have to go on the server/ folder.

EDIT: @serkandurusoy is also a valid way to do it, but in my opinion, this is better to use the client-side createUser function from the accounts-password since it will encrypt the password before sending it to the wire. Also, with autoform hooks, and your schema, you can easily write the form template, then override the submit action to use the Accounts.createUser function.

If you need code hints, let me know.

Hope it helps.


#5

make it optional for insert and required for update


#6

How are you doing that with collection2 ?

I am aware of denyInsert and denyUpdate and custom which can be used to configure an auto-value with this.isInsert or this.isUpdate


#7

you can’t pass user data from autoform directly to the onCreateUser
callback so you still need o wrap that following createUser function
within a method body and pass to the method from autoform.

you can place the method body within a directory that’s available to both
client and server therefore the client insert will be a stub and server
call will pass the password encrypted.

also, if you use an update method (like I suggested before) it will bypass
the callback therefore be more efficient and synchronous, thus much more
predictable for providing feedback to the user.


#8

edit: efficiency might be an overstatement here due to two db calls
compared to a callback.


#9

In fact all of those are valid ways to achieve it but I’d go for autovalue,
and combine isUpdate and isSet. in fact collection2 readme has a very nice
example on its readme that you can use for this. I think it was the
timestamp (createdat/updatedat) example.

sorry, I’m on a painfully slow and old iphone right now so can’t dig that
up :frowning:


#10

Okay, my real bad.
I stuck to the validation problem of the field status, not the need to pass it to createUser. So ye, in this case, the method should be the right choice.

Althought, i think @fire_water should use a default value added with Account.onCreateUser, and then offer to the user the ability to modify it on first login, in a user profile/options panel.


#11

No problems, i understand your point with autovalue. I used it alot for my apps.


#12

I assume it is not a field that he wants the user be able to update themselves.


#13

Hi guys. Didn’t expect my question to be so complex, but thank you for sorting this out. I was wondering if you could please provide a code example of the final solution. Thanks (:


#14

It isnt really complex, i just misunderstood you question.

Here is an example you could use. What you need to do it to create an autoform who will call a method for you. Then, you will use this method to create a user account, and like @serkandurusoy suggested, you will update the user object previously created with the status value.

The first thing is to create the form with autoform. For this, you need a schema for this form. You can’t use the Schema.User schema, since it differ from the data you will expect in the form.

Schema.createUserFormSchema = new SimpleSchema({
    username: {
        type: String,
        regEx: /^[a-z0-9A-Z_]{3,15}$/
    },
    email: {
        type: String,
        regEx: SimpleSchema.RegEx.Email
    },
    password: {
        type: String
        // create a regex here to restrict password to a format
    },
    passwordConfirmation: {
        type: String,
        // this is a custom validation to ensure the password match
        custom: function () {
            if (this.value !== this.field('password').value) {
                return ("passwordMismatch");
            }
        }
    },
    status: {
    	type: String,
    	optional: false
    }
});

/*
 * custom errors message for autoform
 * we use it for the error 'passwordMismatch', since it is a
 * custom validation and autoform have no predefined messages for it
 */

Schema.createUserFormSchema.messages({
    "passwordMismatch": "Passwords do not match",
});
<template name="createUserForm">
  {{#autoForm schema=schema id="createUsertForm" type="method" meteormethod="createUser"}}
  <fieldset>
    <legend>Contact Us</legend>
    {{> afQuickField name="username"}}
    {{> afQuickField name="email"}}
    {{> afQuickField name="status"}}
    {{> afQuickField name="password"}}
    {{> afQuickField name="passwordConfirmation"}}
    <div>
      <button type="submit" class="btn btn-primary">Submit</button>
      <button type="reset" class="btn btn-default">Reset</button>
    </div>
  </fieldset>
  {{/autoForm}}
</template>

This form is very light. Look at the docs for more options, like showing validation errors messages…

You also need this template helper.

Template.createUserForm.helpers({
    schema: function () {
        return Schema.createUserFormSchema;
    }
});

Make sure Schema is global in you app.

Then, here is the method you need to put in server folder of you app.
This method will be called by autoform when all fields are validated on the client, accordingly to the schema.
Be carefull, client validation could be skipped by malicious user, always verify data before using it (with check for example).

Meteor.methods({
  'createUser' : function(doc) {

    check(doc, Schema.createUserFormSchema);
    // `doc` will contains the field who are in the `Schema.createUserFormSchema`
    var newUser = Accounts.createUser(/* standard args */);
    Meteor.users.update(newUser, /* set the extra information, like status */);
  }
});

Hope it helps and i didnt forgot somethind, let me know in case of troubles.

@fire_water
@serkandurusoy


#15

Thank you very much for the helpful explanation. I have a question about location of the Meteor method code. I am currently storing my methods in lib/methods.js as recommended by the Meteor docs at this link. Can I store the createUser method in that location or is that not a smart choice? Thanks.


#16

@fire_water
You have to understand the purpose of the lib directory. Everything in that will be loaded first. But there is another rule that is more important, client and server directories. You can have a lib directory in each of this directories.

So, starting from app root

  • lib/ will be common for client and server, and loaded before everything else
  • client/lib/ this code will run only on client, and will be loaded first
  • server/lib this code will run only on server, and will be loaded first

The meteor doc advice us to store the methods in a common path because if there is db operations in this methods, Meteor can use latency compensation for those methods. But be carefull, if those methods are doing sensitive writes, you should store them in the server/ directory.

Read this good article : https://www.discovermeteor.com/blog/what-goes-where/