Using objects as options in Autoform - mongodb

In my Stacks schema i have a dimensions property defined as such:
dimensions: {
type: [String],
autoform: {
options: function() {
return Dimensions.find().map(function(d) {
return { label: d.name, value: d._id };
});
}
}
}
This works really well, and using Mongol I'm able to see that an attempt to insert data through the form worked well (in this case I chose two dimensions to insert)
However what I really what is data that stores the actual dimension object rather than it's key. Something like this:
[
To try to achieve this I changed type:[String] to type:[DimensionSchema] and value: d._id to value: d. The thinking here that I'm telling the form that I am expecting an object and am now returning the object itself.
However when I run this I get the following error in my console.
Meteor does not currently support objects other than ObjectID as ids
Poking around a little bit and changing type:[DimensionSchema] to type: DimensionSchema I see some new errors in the console (presumably they get buried when the type is an array
So it appears that autoform is trying to take the value I want stored in the database and trying to use that as an id. Any thoughts on the best way to do this?.
For reference here is my DimensionSchema
export const DimensionSchema = new SimpleSchema({
name: {
type: String,
label: "Name"
},
value: {
type: Number,
decimal: true,
label: "Value",
min: 0
},
tol: {
type: Number,
decimal: true,
label: "Tolerance"
},
author: {
type: String,
label: "Author",
autoValue: function() {
return this.userId
},
autoform: {
type: "hidden"
}
},
createdAt: {
type: Date,
label: "Created At",
autoValue: function() {
return new Date()
},
autoform: {
type: "hidden"
}
}
})

According to my experience and aldeed himself in this issue, autoform is not very friendly to fields that are arrays of objects.
I would generally advise against embedding this data in such a way. It makes the data more difficult to maintain in case a dimension document is modified in the future.
alternatives
You can use a package like publish-composite to create a reactive-join in a publication, while only embedding the _ids in the stack documents.
You can use something like the PeerDB package to do the de-normalization for you, which will also update nested documents for you. Take into account that it comes with a learning curve.
Manually code the specific forms that cannot be easily created with AutoForm. This gives you maximum control and sometimes it is easier than all of the tinkering.
if you insist on using AutoForm
While it may be possible to create a custom input type (via AutoForm.addInputType()), I would not recommend it. It would require you to create a template and modify the data in its valueOut method and it would not be very easy to generate edit forms.
Since this is a specific use case, I believe that the best approach is to use a slightly modified schema and handle the data in a Meteor method.
Define a schema with an array of strings:
export const StacksSchemaSubset = new SimpleSchema({
desc: {
type: String
},
...
dimensions: {
type: [String],
autoform: {
options: function() {
return Dimensions.find().map(function(d) {
return { label: d.name, value: d._id };
});
}
}
}
});
Then, render a quickForm, specifying a schema and a method:
<template name="StacksForm">
{{> quickForm
schema=reducedSchema
id="createStack"
type="method"
meteormethod="createStack"
omitFields="createdAt"
}}
</template>
And define the appropriate helper to deliver the schema:
Template.StacksForm.helpers({
reducedSchema() {
return StacksSchemaSubset;
}
});
And on the server, define the method and mutate the data before inserting.
Meteor.methods({
createStack(data) {
// validate data
const dims = Dimensions.find({_id: {$in: data.dimensions}}).fetch(); // specify fields if needed
data.dimensions = dims;
Stacks.insert(data);
}
});

The only thing i can advise at this moment (if the values doesnt support object type), is to convert object into string(i.e. serialized string) and set that as the value for "dimensions" key (instead of object) and save that into DB.
And while getting back from db, just unserialize that value (string) into object again.

Related

One way association with array of reference _id in sails js

I'm trying to use one way association because I need only to have reference from 1 model to other model but not vice versa.
Model Arts:
module.exports = {
attributes: {
fileName: {type: 'string', required: true},
softwareUsed: {
model: 'Softwares'
}
}
}
Model Softwares:
module.exports = {
attributes: {
name: {type: 'string', required: true}
}
}
This is my api:
http://localhost:1337/api/v1/arts/create
if this is my request body, it works fine:
request body:
{
"fileName": "booking.jpeg",
"softwareUsed": "5e70309cbf12b61299d6c528",
}
but i want to store array of softwareUsed, so i tried:
request body:
{
"fileName": "booking.jpeg",
"softwareUsed": ["5e70309cbf12b61299d6c528", "5e70309cbf12b61299d6c529"],
}
but i got an error with that:
error: OperationalError [UsageError]: Invalid new record.
Details:
Could not use specified `softwareUsed`. Expecting an id representing the associated record, or `null` to indicate there will be no associated record. But the specified value is not a valid `softwareUsed`. Instead of a string (the expected pk type), the provided value is: [ '5e70309cbf12b61299d6c528', '5e70309cbf12b61299d6c529' ]
I also tried to make it array in model:
softwareUsed: [{
model: 'Softwares'
}]
but still don't work.
Is there a way to that in one way association or I need to use other association, but how can I achieve that?
Thank you.
I think you need to label the softwareUsed attribute with a collection, not a model:
module.exports = {
attributes: {
fileName: {type: 'string', required: true},
softwareUsed: {
collection: 'Softwares'
}
}
}
All the documentation on one-to-many in the sails docs involves two-way associations and adding a via attribute, but I think this way works for a one-way association.
Of course, your first api call may now longer work: you may need to wrap the single software id in an array.

Meteor prefill a collection

I have made the following collection in meteor:
CodesData = new Mongo.Collection('CodesData');
CodesDataSchema = new SimpleSchema({
code: {
label: "Code",
type: Number
},
desc: {
label: "Description",
type: String,
}
});
CodesData.attachSchema(CodesDataSchema);
Now I want to prefill this collection with some data.
For example: code: 1 desc: "hello".
How can I do this manually and easily?
You can use Meteor.startup to run some actions on your collection once the server app has been loaded and is starting:
CodesData = new Mongo.Collection('CodesData');
CodesDataSchema = new SimpleSchema({ code: { label: "Code", type: Number }, desc: { label: "Description", type: String, } });
.attachSchema(CodesDataSchema);
Meteor.startup(()=>{
// Only fill if empty, otherwise
// It would fill on each startup
if (CodesData.find().count() === 0) {
CodesData.insert({ code: 1, description: 'some description' });
}
});
If you have a lot of data to prefill you can define it in a JSON and load it on startup:
Consider the following json named as pre:
{
codesdata: [
{ code: 1, description: 'foo' },
{ code: 7, description: 'bar' }
]
}
Meteor.startup(()=>{
const preData = JSON.parse( pre );
preData.codesData.forEach( entry => {
CodesData.insert( entry );
});
});
This allows you to manage your prefill more easily and also let's you version control the json if desired ( and no sensitive data is revealed ).
Considerations:
The function Meteor.startup runs on each start. So you should consider how to avoid unnecessary inserts / prefill that create doubles. A good way is to check if the collection is empty (see first example).
You may put the startup code an another js file in order to separate definitions from startup routines.
The current script does not differentiate between server or client. You should consider to do this on server and create a publication / subscription around it.
More readings:
https://docs.meteor.com/api/core.html#Meteor-startup
Importing a JSON file in Meteor
https://docs.meteor.com/api/core.html#Meteor-settings

Updating an array field in a mongodb collection

I am trying to update my collection which has an array field(initially blank) and for this I am trying this code
Industry.update({_id:industryId},
{$push:{categories: id:categoryId,
label:newCategory,
value:newCategory }}}});
No error is shown, but in my collection just empty documents({}) are created.
Note: I have both categoryId and newCategory, so no issues with that.
Thanks in advance.
This is the schema:
Industry = new Meteor.Collection("industry");
Industry.attachSchema(new SimpleSchema({
label:{
type:String
},
value:{
type:String
},
categories:{
type: [Object]
}
}));
I am not sure but maybe the error is occuring because you are not validating 'categories' in your schema. Try adding a 'blackbox:true' to your 'categories' so that it accepts any types of objects.
Industry.attachSchema(new SimpleSchema({
label: {
type: String
},
value: {
type: String
},
categories: {
type: [Object],
blackbox:true // allows all objects
}
}));
Once you've done that try adding values to it like this
var newObject = {
id: categoryId,
label: newCategory,
value: newCategory
}
Industry.update({
_id: industryId
}, {
$push: {
categories: newObject //newObject can be anything
}
});
This would allow you to add any kind of object into the categories field.
But you mentioned in a comment that categories is also another collection.
If you already have a SimpleSchema for categories then you could validate the categories field to only accept objects that match with the SimpleSchema for categories like this
Industry.attachSchema(new SimpleSchema({
label: {
type: String
},
value: {
type: String
},
categories: {
type: [categoriesSchema] // replace categoriesSchema by name of SimpleSchema for categories
}
}));
In this case only objects that match categoriesSchema will be allowed into categories field. Any other type would be filtered out. Also you wouldnt get any error on console for trying to insert other types.(which is what i think is happening when you try to insert now as no validation is specified)
EDIT : EXPLANATION OF ANSWER
In a SimpleSchema when you define an array of objects you have to validate it,ie, you have to tell it what objects it can accept and what it can't.
For example when you define it like
...
categories: {
type: [categoriesSchema] // Correct
}
it means that objects that are similar in structure to those in another SimpleSchema named categoriesSchema only can be inserted into it. According to your example any object you try to insert should be of this format
{
id: categoryId,
label: newCategory,
value: newCategory
}
Any object that isn't of this format will be rejected while insert. Thats why all objects you tried to insert where rejected when you tried initially with your schema structured like this
...
categories: {
type: [Object] // Not correct as there is no SimpleSchema named 'Object' to match with
}
Blackbox:true
Now, lets say you don't what your object to be filtered and want all objects to be inserted without validation.
Thats where setting "blackbox:true" comes in. If you define a field like this
...
categories: {
type: [Object], // Correct
blackbox:true
}
it means that categories can be any object and need not be validated with respect to some other SimpleSchema. So whatever you try to insert gets accepted.
If you run this query in mongo shell, it will produce a log like matched:1, updated:0. Please check what you will get . if matched is 0, it means that your input query is not having any matching documents.

Mongoose OR operator for schema definitions

Does Mongoose support, or is there an available package that supports multiple "options" for the embedded schemas in an array?
For example, the things property can contain only one of two schemas:
new Schema({
things: [{
requiredProp: String,
otherProp: Number
}, {
otherOption: Number
}]
});
In other words, I do not want to just allow anything (AKA Schema.Types.Mixed) to be stored in this property, but only these two possible definitions.
Or, do schema design recommendations exist to avoid this problem?
You should only define one dict in the array type of the schema, and then set if they are required or not with the mongoose schema types logic. Use pre save if you want to do more logic to assure that either one of the fields have been set, like this:
var MySchema = new Schema({
things: [{
requiredProp: {type: String, required: true},
otherProp: Number,
otherOption: Number,
}]
});
MySchema.pre('save', function(next) {
if (!this.otherProp && !this.otherOption) {
next(new Error('Both otherProp and otherOption can\'t be null'))
} else {
next()
}
})
Opon saving the object it will return an error if neither otherProp nor otherOption has been set.

How can I access a date typed field from an ExtJS JSonStore?

I've been trying to retrieve a date value and an integer value from the database, using the following code:
var l_alsChampsMois, l_stoDonneesMois;
try {
l_alsChampsMois = [
{name: "date_mois", type: "date", dateFormat: "Y-m-d"},
{name: "indice", type: "integer"}
];
l_stoDonneesMois = new Ext.data.JsonStore({
fields: l_alsChampsMois,
autoLoad: false,
proxy: {
type: "ajax",
url: "/commun/req_sql/req_ind_per_mois.php",
reader: {
type: "json",
root: "rows"
},
// some configs to use jsFiddle echo service (you will remove them)
actionMethods: {
read: "POST"
},
extraParams: {
key:"test"
}
},
listeners: {
load: function(objStore, alsLignes, blnOk, objOptions) {
window.alert("Mois fin : " + objStore.getAt(0).get("date_mois"));
}
}
});
l_stoDonneesMois.load({params: {
p_idsoc: l_strIdSociete,
p_mois: l_datDebut.getMonth() + 1,
// getMonth renvoie 0 pour janvier, etc.
p_annee: l_datDebut.getFullYear(),
p_debut: 1,
p_etape: 1
}
});
with l_strIdSociete and l_datDebut being variables previously assigned and /commun/req_sql/req_ind_per_mois.php the PHP page that retrieves the data and converts it to JSON.
It seems to work fine (indeed, Firebug tells me the load does retrieve a data structure with "date_mois" and "indice" containing the values I expect them to), only the window.alert returns undefined. If I replace "date_mois" with "indice", it returns the expected value for "indice".
I've tried to use objStore.getAt(0).getData()["date_mois"], to no avail.
My only clue about this is that "date_mois" in the data structure shown by Firebug is an Object, but even so it shouldn't be undefined, now should it? I looked up http://docs.sencha.com/ext-js/4-1/#!/api/Ext.data.Field-cfg-type that wasn't exactly forthcoming with straight answers.
So what did I do wrong there?
If you need current time you can use php time function(note: it returns seconds, JS uses milliseconds), in another case you need to convert by mktime function.