Meteor prefill a collection - mongodb

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

Related

How to update an array of objects in firestore? [duplicate]

I have a document in Firebase Firestore that is something like the below. The main point here is that I have an array called items with objects inside it:
{
name: 'Foo',
items: [
{
name: 'Bar',
meta: {
image: 'xyz.png',
description: 'hello world'
}
},
{
name: 'Rawr',
meta: {
image: 'abc.png',
description: 'hello tom'
}
}
]
}
I am trying to update a field inside the item array, under the meta object. For example items[0].meta.description from hello world to hello bar
Initially I attempted to do this:
const key = `items.${this.state.index}.meta.description`
const property = `hello bar`;
this.design.update({
[key]: property
})
.then(() => {
console.log("done")
})
.catch(function(error) {
message.error(error.message);
});
This didn't appear to work though, as it removed everything in the item index I wanted to modify, and just kept the description under the meta object
I am now trying the following which basically rewrites the whole meta object with the new data
const key = `items.${this.state.index}.meta`
const property = e.target.value;
let meta = this.state.meta;
meta[e.target.id] = property;
this.design.update({
[key]: meta
})
.then(() => {
this.setState({
[key]: meta
})
})
.catch(function(error) {
message.error(error.message);
});
Unfortunately though, this seems to turn my whole items array into an object that looks something like:
{
name: 'Foo',
items: {
0: {
name: 'Bar',
meta: {
image: 'xyz.png',
description: 'hello world'
}
},
1: {
name: 'Rawr',
meta: {
image: 'abc.png',
description: 'hello tom'
}
}
}
}
Any ideas how I can just update the content I want to?
Firestore doesn't have the ability to update an existing element in an indexed array. Your only array options for updates are described in the documentation - you can add a new element to the array ("arrayUnion") or remove an element ("arrayRemove").
As an alternative, you can read the entire array out of the document, make modifications to it in memory, then update the modified array field entirely.
You can make a separate collection for that particular array, like this in this picture earlier I had different fields (no collections) of name, email and pages, And in this, I wanted to change the data of a specific page that is inside the array. For that, I made a different collection of pages with individual documents of each page having values of title description and content which can be mutated.

Using objects as options in Autoform

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.

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.

Store contents with rest proxy giving incorrect count

ExtJS 5.1.x, with several stores using rest proxy.
Here is an example:
Ext.define('cardioCatalogQT.store.TestResults', {
extend: 'Ext.data.Store',
alias: 'store.TestResults',
config:{
fields: [
{name: 'attribute', type: 'string'},
{name: 'sid', type: 'string'},
{name: 'value_s', type: 'string'},
{name: 'value_d', type: 'string'}
],
model: 'cardioCatalogQT.model.TestResult',
storeId: 'TestResults',
autoLoad: true,
pageSize: undefined,
proxy: {
url: 'http://127.0.0.1:5000/remote_results_get',
type: 'rest',
reader: {
type: 'json',
rootProperty: 'results'
}
}
}
});
This store gets populated when certain things happen in the API. After the store is populated, I need to do some basic things, like count the number of distinct instances of an attribute, say sid, which I do as follows:
test_store = Ext.getStore('TestResults');
n = test_store.collect('sid').length);
The problem is that I have to refresh the browser to get the correct value of 'n,' otherwise, the count is not right. I am doing a test_store.load() and indeed, the request is being sent to the server after the .load() is issued.
I am directly querying the backend database to see what data are there in the table and to get a count to compare to the value given by test_store.collect('sid').length);. The strange thing is that I am also printing out the store object in the debugger, and the expected records (when compared to the content in the database table) are displayed under data.items array, but the value given by test_store.collect('sid').length is not right.
This is all done sequentially in a success callback. I am wondering if there is some sort of asynchronous behavior giving me the inconsistent results between what is is the store and the count on the content of the store?
I tested this with another store that uses the rest proxy and it has the same behavior. On the other hand, using the localStorage proxy gives the correct count consistent with the store records/model instances.
Here is the relevant code in question, an Ajax request fires off and does its thing correctly, and hit this success callback. There really isn't very much interesting going on... the problem section is after the console.log('TEST STORE HERE'); where I get the store, print the contents of the store, load/sync then print the store (which works just fine) and then finally print the length of uniquely grouped items by the sid attribute (which is what is not working):
success: function(response) {
json = Ext.decode(response.responseText);
if(json !== null && typeof (json) !== 'undefined'){
for (i = 0, max = json.items.length; i < max; i += 1) {
if (print_all) {
records.push({
sid: json.items[i].sid,
attribute: json.items[i].attribute,
string: json.items[i].value_s,
number: json.items[i].value_d
});
}
else {
records.push({
sid: json.items[i].sid
})
}
}
//update store with data
store.add(records);
store.sync();
// only add to store if adding to search grid
if (!print_all) {
source.add({
key: payload.key,
type: payload.type,
description: payload.description,
criteria: payload.criteria,
atom: payload.atom,
n: store.collect('sid').length // get length of array for unique sids
});
source.sync();
}
console.log('TEST STORE HERE');
test_store = Ext.getStore('TestResults');
test_store.load();
test_store.sync();
console.log(test_store);
console.log(test_store.collect('sid').length)
}
// update grid store content
Ext.StoreMgr.get('Payload').load();
Ext.ComponentQuery.query('#searchGrid')[0].getStore().load();
}
For completeness, here is the data.items array output items:Array[2886]
which is equivalent count of unique items grouped by the attribute sid and finally the output of console.log(test_store.collect('sid').length), which gives the value from the PREVIOUS run of this: 3114...

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.