What is the best way to exclude a field value from the data in Meteor when using Autoform, SimpleSchema, Collection2, etc? Say I have:
MySchema = new SimpleSchema({
password: {
type: String,
label: "Enter a password",
min: 8
},
confirmPassword: {
type: String,
label: "Enter the password again",
min: 8,
custom: function () {
if (this.value !== this.field('password').value) {
return "passwordMismatch";
}
}
}
});
... and I do not want to confirmPassword field persisted to the database, what is the best way to handle that? I assume using hooks, but if so where and how? Hopefully there is a way to just exclude one (or more) values without having to redefine the whole whole schema to say which to include and which to exclude. If I have 100 fields and want to exlcude 1, hopefullly the hook or whatever does not need the other 99 defiled too.
TIA
With autoform, you must use a method on the server side. Simply delete the field in the method code when you receive it at the server before inserting the document.
Related
I have a user profile schema that looks sort of like this:
Schema.UserProfile = new SimpleSchema({
name: {
type: String,
optional: false
},
gender: {
type: String,
optional: false,
allowedValues: ['Male', 'Female']
},
location: {
type: String,
optional: false
},
age: {
type: Number,
optional: false
}
}
My registration form requires the user to select their gender on it. Once registered, the user is presented with another form to fill out the rest of the data: name, location, and age.
Here's the problem: When the user tries to register, I get the error: Name is required. The user isn't suppose to enter their name until they register.
What is the proper approach to only validating data that is actually being saved on a specific form?
EDIT
Here is my registration script:
var data = {};
var profile = {};
data.email = $('#email').val();
data.password = $('#password').val();
profile.gender = $('#gender').val();
data.profile = profile;
Accounts.createUser(data, function (error) {
// do
// ERROR: Name is required
});
When the user actually registers, they're presented with a form to fill the other data in. The form submit calls a method that the tries to update the profile object.
By using the option optional: false you are making it mandatory whenever creating the object.
One solution could be to use myCollection.insert(); with the flag validate: false instead of createUser().
e.g.
myCollection.insert(doc, {validate: false});
This will not trigger the error, since is skipping the validation.
I would personally not use this way, but it might be a solution.
You can use SimpleSchema's pick command to select certain elements from a schema. That way you can have multiple schemas derived from the same original schema definition.
Schemas.UserProfileRegistration1 = Schemas.UserProfile.pick(['gender']);
Schemas.UserProfileRegistration2 = Schemas.UserProfile.pick(['name', 'location', 'age']);
https://github.com/aldeed/meteor-simple-schema#extracting-simpleschemas
I'm using collection2 and I'm trying to get it to handle validation is a specific way. I have a profile schema which looks kind of like this:
Schema.UserProfile = new SimpleSchema({
name: {
type: String,
optional: false
}
location: {
type: String,
optional: true
}
gender: {
type: String,
optional: false
}
});
Schema.User = new SimpleSchema({
username: {
type: String,
optional: true
},
emails: {
type: Array,
optional: true
},
"emails.$": {
type: Object
},
"emails.$.address": {
type: String,
regEx: SimpleSchema.RegEx.Email
},
"emails.$.verified": {
type: Boolean
},
createdAt: {
type: Date
},
profile: {
type: Schema.UserProfile,
optional: true
},
services: {
type: Object,
optional: true,
blackbox: true
},
roles: {
type: [String],
optional: true
},
heartbeat: {
type: Date,
optional: true
}
});
Meteor.users.attachSchema(Schema.User);
Now, on my registration form I'm requiring the user to select their gender and then later once they log in, users are presented with a separate form asking for their name and location. Here's the problem:
The registration form works and everything goes through with saving. When they try to save the internal form with location and name though I get an error:
Error invoking Method 'updateProfile': Gender is required [400]
I know it's happening because it's required in the schema but I've already obtained this information. How do I not require that? Or do I set up validation per form?
You have to add validation through jquery or you can use toaster for display the error on client side.
Read this also : link
I assume you use aldeed:autoform for your forms. When you use normal type in the form, all fields, even those already filled marked as mandatory have to be submitted. Two ways to fix this:
Dirty way: set hidden field with the prefilled value.
You can also set your form type as update as seen in the doc. This way, simple-schema will validate your newDoc already filled with your previous entries without screaming.
The solution number two is the one I use in most cases. This plus autoform's hooks give you enough flexibility to adapt to most use-cases you might encounter.
I don't know whether or not it is a more elegant solution, but we've stopped attaching simpleSchemas to documents in our current project.
We instead have different schemas in each collection's namespace, one for checking user input on insert, one on update, and and one to be used to fill defaultValue when inserting a new doc (which can be done either by the client or the server, in which case we don't check for input). We call .validate() or .clean() depending on what we want to do.
With clever use of the possibility to build schemas from array of schemas, we're not writing bigger schemas in the end (there's more of them though), but we have total control on when we check, and which fields are checked.
From the SimpleSchema docs:
Say you have a required key "friends.address.city" but
"friends.address" is optional. If "friends.address" is set in the
object you're validating, but "friends.address.city" is not, there is
a validation error. However, if "friends.address" is not set, then
there is no validation error for "friends.address.city" because the
object it belongs to is not present.
So the error happens because you're including profile on both forms and gender is not optional within profile. I can think of two ways to solve this:
Have additional objects under profile that are both optional and contain required fields for name/location on one (though it seems like location might be optional in both scenarios based on your code) and a required field for gender on the other. I don't particularly like this solution but it prevents needing form validation.
Use jQuery form validation (I use the package themeteorchef:jquery-validation) and make all your fields in profile optional.
It also looks like SimpleSchema accepts a function for the optional property, so you could use some custom logic there - maybe you get arguments or a context in that function that will allow you to do what you want?
Hope that helps!
I have a simple todo schema: (just a sample to draw my question)
{
title: {
type: string
},
value: {
type: string
},
author: {
type: object
},
"author._id": {
type: string
},
"author.firstName": {
type: string
},
"author.lastName": {
type: string
},
}
The author entries are from meteor.user. If the meteor user changes the firstName or lastName i have to update the todo. I have two possibilities:
observerChanges (server side) to users collection and update all todos from this user with the new firstname/lastname
if i call the user update method i can call a method to update all todos
when it's better to use cursor.observeChanges and when it's better to call a update method manual? And why?
As the comment says, you should not store the author name / email in the document if it is mutable:
Store the ID of the user only in the document, the UserID is immutable.
When building your ToDo template, look up the User information by ID: you would need to publish a Publication for user by Id, and subscribe to it on the client with the userId as parameter.
Meteor.publish('userById', function(userId) {
return Meteor.users.find({_id: userId}, {limit:1});
});
in your route / template.onCreated depending on your Router, assuming the document is called doc
this.subscribe('userById', this.doc.author._id);
in the template helper
Template.todoTemplate.helpers({
'Author': function() {
return Meteor.users.findOne({_id: this.doc.author._id});
}
});
and call the Author info in the template
<Template name="todoTemplate">
First Name: {{Author.first_name}}
Last Name: {{Author.last_name}}
</Template>
I think you shouldn't rely on the second method, because sometimes you (or your teammate) might forget to update it. Moreover, if you're denormalizing user data in other collections, users knowing Meteor might just call your Meteor.method or manipulate db from the browser console...
You can use this package:
meteor add matb33:collection-hooks
It adds some hooks to your mongo insert/update/remove call
For example:
Meteor.users.after.update(function (userId, doc, fieldNames, modifier, options) {
if (this.previous.firstName === doc.firstName && this.previous.lastName === doc.lastName) {
return;
}
Todos.update({'author._id': doc._id}, {
$set: {
'author.firstName': doc.firstName,
'author.lastName': doc.lastName,
}
})
}, {fetchPrevious: true})
(To update the Todos collection efficiently, make sure to add index to author field)
This is just a handier way than writing your own observeChanges, and better than manually updating Todos collection every time you update the users collection, because you might forgot to call it in some case, or some hacker user just calls Meteor.users.update(Meteor.userId(), {...}) perhaps...
But still, I think you should always add some auto-correct mechanism to avoid wrong data being displayed, because no matter which method you choose, some error will occur (maybe the server watching the db just crashes right after users update). You can check on the client side when displaying content, if author.firstName doesn't match Meteor.users.findOne(author._id) (but you have to publish the user though...), than call a method to tell the server to update it.
I've been struggling with this for a couple of hours and I can't find a good solution so maybe someone can shed a light on this.
I have a simple schema like this:
var groupschema = new SimpleSchema({
name: {
label: 'Name',
type: String
},
description: {
label: 'Description',
type: String
}
}
And I have another one:
var itemschema = new SimpleSchema({
name: {
label: 'Type:',
type: String
},
details: {
label: 'Details',
type: String
},
group: [groupschema] // <--- this is my main interest
}
If I use the above code sample AutoForm will generate an "inner form" which is quite cool actually for some puporse (e.g. for a contact to have an array of adresses or phone numbers) but for me I would like a drop-down select with the name of the group and when I click on the Insert/Update button (depending on the current form) I would like to add the whole group document to the inserted/updated item document as a subdocument.
Something like this will be inserted to the mongodb:
{
_id: the generated mongoid,
name: "This is a type",
details: "There are some thing to know about this type",
group:{
name: "Cool group",
description: "This is a really cool group"
}
}
The actual documents are far more complicated the above example is just an oversimplified version.
I've stopped writing this question yesterday and tried to do my own version.
My - half baked - solution is:
introducing a new field in the schema named groupselect (type string, autoform type: select)
populate it's contents with a Collection.find().map() lookup
groupselect: {
type: String,
label: 'Group',
optional: true,
blackbox: true,
autoform:{
type: "select",
options : function() {
return Issuegroup.find().map(function (c) {
return {label: c.name , value: c._id};
});
}
}
},
using autoform hooks before insert I assign the subdocument to the real fiel group = Group.find({_id:doc.groupselect}) and I remove the helper field from the doc
using the same technique in the before update hook also for an update form
The problem I seem to be unable to solve in a clean way is to set the default value of the helper field 'groupselect' when the update form displays. I've tried the docToForm hooks but no luck.
Isn't this somehow a very common problem? I imagine there has to be a proper solution for this so I bet that I am missing something very obvious and hopefully someone will point it out for me.
Thanks
I am using Simple Schema,collection hooks and Autoform packages in Meteor and I am trying to update a Embedded object in my schema in the after update collection hook. I feel I might doing something silly, but was just unable to solve this problem. I am getting the exeption while saving: Exception while invoking method '/providers/update' Error: 0 must be an integer
My schema:
Schemas.Providers = new SimpleSchema({
email: {
type: String,
label: 'Email Address',
autoValue: function() {
if (this.isInsert || this.isUpdate) {
return Meteor.user().emails[0].address;
}
},
autoform: {
afFieldInput: {
type: 'email'
}
}
},
officelocation: {
type: String,
label: 'Location of Office',
optional:true
},
location: {
type: [LocationSchema],
optional:true
}});
The collection hook that works:
Providers.after.update(function (userId, doc) {
var oldDoc = this.previous;
Providers.direct.update({_id: doc._id},{$set: {location:{
type:'Point',
coordinates:[0,0]
}}});
});
The collection hook that does not work.. Ideally I should not be updating after the collection update, but wanted to make sure this works:
Providers.after.update(function (userId, doc) {
var oldDoc = this.previous;
var array=[];
var iArray=doc.officelocation.split(",");
array.push(Number(iArray[1]));
array.push(Number(iArray[0]))
Providers.direct.update({_id: doc._id},{$set: {location:[{
type:'Point',
coordinates:array
}]}});
});
Looking at what you are trying to store, use parseInt instead of Number that you are using. It will return an integer that mongo can store.
The issue is not with the method that you are using. It is with the way you are storing data in mongo. Show us how your LocationSchema looks like.
Detail:
Mongo uses a different number format that what javascript uses. In javascript, a Number can be an integer, a float, a decimal or anything that you want. Mongo has very strict demands when it comes to integer or floats.
Overall, what it means is that if you want to store an accurate decimal number in mongo (which I suspect what you are trying to do), you have to either store it as a string (you loose the ability to do direct mongo operation such as $inc $gt etc) or divide it into two parts and store separably. The third option is to store it as a float which isn't accurate an is only useful if you want some kind of approximate value.