Update any user by calling server method - too complicated?


#1

Hi,
I am adding a feature to a test app in meteor which allows admin users to edit other users details. I want to do this using a server method, so I can perform a bunch of validation (using meteor-roles package). I manage to patch a solution, but to me looks like quite a lot of code for something that should be simple. Can anyone take a look and point me in the right direction?
Here’s the server method:

Meteor.methods({
    updateUser: function(targetUserId, fn, ln) {
      check(targetUserId, String);
      check(fn, String);
      check(ln, String);
      if (Roles.userIsInRole(Meteor.user(), ["admin"])) {
        Users.update({
          _id: targetUserId
        }, {
          $set: {
            'profile.firstname': fn,
            'profile.lastname': ln
          }
        })
      } else throw new Meteor.Error(403, "Not authorized to create new users");
    }
  });

And here’s the client code (the form is in a modal dialog and the _id is stored in a Session variable):

var getEditedUser = function() {
    var userId = Session.get('selectedUserId'),
        user = Meteor.users.findOne({
            _id: userId
        })
    return user;
};

Template.ModalEditClient.helpers({
    user: getEditedUser
});

Template.ModalEditClient.events({
    'click #saveEditUser': function(event, template) {
        event.preventDefault();
        var user = getEditedUser();
        var firstname = template.find('#firstname').value;
        var lastname = template.find('#lastname').value;
        firstname = (firstname) ? firstname : user.profile.firstname;
        lastname = (lastname) ? lastname : user.profile.lastname;
        swal({
                title: "Are you sure?",
                text: "This will update user's details!",
                type: "warning",
                showCancelButton: true,
                confirmButtonColor: "#DD6B55",
                confirmButtonText: "Yes, update it!"
            },
            function() {
                var userId = Session.get('selectedUserId')
                Meteor.call('updateUser', userId, firstname, lastname, function(err, result) {
                    if (err) console.log(err)
                });
                $("#editUser").modal("hide");
            });
    }
});

Problems:

  1. I am doing tricks so I don’t update the user when data is not provided in the form, this is not really great.
  2. I am calling the Meteor.users.find() method twice, once for the template helper, once for getting the current user data in the event handler -> not optimal…
  3. I wanted to send a Javascript object to the update server method, but for some reason I am getting undefined when actually calling it…

I am pretty sure this can be done 1001 times more elegantly. Help?


#2

About point 2, you typically get the user from the template context (using this), hence calling Meteor.users.findOne() only once. Please show me your html if you need more guidance on this.


#3

a roles check in allow/deny would work just as well as doing server calls explicitly without the need for extra code.


#4

Looks fine to me.

Generally there are two ways:

  1. Use the collection methods and use allow / deny rules for security.
  2. Use Meteor methods and define the security rules in the method (like you did)

Option 1 also uses Meteor.methods internally. But this is hidden from the API.

  1. I am calling the Meteor.users.find() method twice, once for the template helper, once for getting the current user data in the event handler -> not optimal…

This is not really a problem, because the query is against the in-memory database on the client (minimongo).

  1. I wanted to send a Javascript object to the update server method, but for some reason I am getting undefined when actually calling it…

You can send anything that is EJSON serializable. Plain old JavaScript objects should work.


#5

Hey Steve,
somehow, I don’t understand why, I cannot access the current object from template in events block.
The template is (a snippet):

<template name="ModalEditUser">
    <div class="modal fade" id="editUser" tabindex="-1" role="dialog" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
            {{#with user}}
                <div class="modal-header text-center">
                    <h4 class="modal-title">Edit user: {{profile.firstname}}&nbsp;{{profile.lastname}}</h4>
                </div>

I tried accessing the user being edited in template from events like this:

var user = template.user;

and

var user = template.instance().user;

Neither is working. This template is slightly tricky because is a Bootstrap modal div, so it does not get destroyed when I close it, I think. But it’s not related to this, anyway.


#6

template.user: refers to the template instance.
template.data.user: refers to the template initial context.
this.user: refers to the latest context (set up by #with or #each) -> this is what you need to use


#7

Still getting undefined for this.user and template.user. template.data.user is null…
Maybe I am not attaching properly the user to be edited to the template… It’s a table displaying the users with an Edit button for each row. And here’s the event method:

Template.adminusers.events({
	'click tr': function(e) {
		if ($(e.target).hasClass('btn')) {
			var user = this;
			e.preventDefault();
			Session.set('selectedUserId', user._id);
			$("#editUser").modal("show");
		}
	}
});