I am making a mongodb model>>
const mongoose = require('mongoose');
const {Schema} = mongoose;
const locationSchema = new Schema({
name: String,
Address: String,
ContactInfo: {
phone: Number,
email: String,
},
Website: String,
Hours: {
DaysOpen: String,
OpeningTime:[Number],
ClosingTime:[Number],
},
Services: String,
Languages: String,
Documentation: Boolean,
OtherNotes: String,
})
mongoose.model('Locations', locationSchema);
When I try and run a get request to see what is in my database I am returned
{
"error": false,
"location": {
"Hours": {
"OpeningTime": [
1215,
898
],
"ClosingTime": [
1400
],
"DaysOpen": "Sunday"
},
"_id": "5ee8fd2e57aa5126d4c1c854",
"name": "Evergreen Christian Center Food Pantry",
"Address": "4400 NW Glencoe Rd, Hillsboro, OR 97124",
"ContactInfo": {
"phone": 5033196590,
"email": "gonzocyn2#msn.com"
},
"Website": "https://www.ecc4.org/home",
"Services": "All foods available including meat and frozen foods",
"Languages": "English, Spanish",
"Documentation": false,
"OtherNotes": "Bring own bag or box. Sign up starts at 9:00am",
"__v": 0
}
The problem is that "Hours" is being displayed before the name, address, and contact info. This only occurs when I have the fields "OpeningTime" and "ClosingTime" as arrays. Any idea on how to fix this?
Related
I am trying to learn to react.js with hooks. I have encountered an issue with an array of objects.
Here's the code
const [stories, setStoreis] = useState(
[
{
name: "1st Story",
sid: "1",
appartmentDetails: [
{
aptName: "Master Bath",
status: "",
media: Logo,
},
{
aptName: "Master Bath",
status: "",
media: Logo,
},
],
},
{
name: "2nd Story",
sid: "2",
appartmentDetails: [
{
aptName: "Master Bath",
status: "",
media: Logo,
},
],
},
]
);
To add new Story here's what I am doing
const newStory = {
name: "3rd Story",
sid: "3",
appartmentDetails: [],
};
setStory([...stories, newStory]);
It's adding completely fine. Now I want to add appartmentDetails. How can I add appartmentDetails of specific story? Also if I want to update how to do it?
If you are working with nested data like this with immutability in mind, I'd suggest using Immer as it makes cases like this much easier. There's a use-immer hook that is very useful to this.
Edit: Codesandbox with an example of use-immer with create/update/delete on the story data.
import { useImmer } from 'use-immer';
const [stories, updateStories] = useImmer(
[
{
name: "1st Story",
sid: "1",
appartmentDetails: [
{
aptName: "Master Bath",
status: "",
media: Logo,
},
{
aptName: "Master Bath",
status: "",
media: Logo,
},
],
},
{
name: "2nd Story",
sid: "2",
appartmentDetails: [
{
aptName: "Master Bath",
status: "",
media: Logo,
},
],
},
]
);
Your add story code can remain exactly the same:
const newStory = {
name: "3rd Story",
sid: "3",
apartmentDetails: [],
};
updateStories([...stories, newStory]);
Pushing to apartmentDetails:
updateStories(draft=>{draft[index].apartmentDetails.push({aptName: 'New Apartment'})});
Updating apartmentDetails:
updateStories(draft=>{
const story = draft.find(story=>story.sid === '2');
story.apartmentDetails[0].aptName = 'Better Bath'
});
If you don't want an external library it's much more complicated to work with:
setStories(stories=>stories.map((story,index)=>index===changeIndex? {...story, apartmentDetails: story.apartmentDetails.concat([{aptName: 'New Apartment'}])} :story));
You can find a specific story using Array.find:
const story = stories.find(story => story.sid === '3');
const storyWithDetails = {...story, appartmentDetails: [{aptName: "Master Bath"}]};
To manipulate existing appartmentDetails array you can use array methods like:
const storyWithDetails = {...story, appartmentDetails: story.appartmentDetails.filter()]};
const storyWithDetails = {...story, appartmentDetails: story.appartmentDetails.map()]};
const storyWithDetails = {...story, appartmentDetails: story.appartmentDetails.slice()]}
... and so on. Then put it back into state like:
const otherStoris = stories.filter(story => story.sid !== '3');
setStory([...otherStoris, storyWithDetails]);
#Zachary Haber, i am adding changeIndex and its saying; "cant find variable changeIndex"
Given a collection of documents similar to the following document
{
"_id": {
"$oid": "60582f08bf1d636f4b762ebc"
}
"experience": [{
"title": "Senior Customer Success Account Manager",
"company": "Microsoft",
"tenure": 8
}, {
"title": "Senior Service Delivery Manager",
"company": "Microsoft",
"tenure": 34
}],
"company3": "Oracle",
"tenure3": 10,
"title3": "Senior Customer Success Manager - EMEA |Oracle Marketing Cloud"
}
How would I write an updateMany or other shell command to move company3, tenure3 and title3 inside the experience array as a new object {company: <company3 value>, title: <title3 value>, tenure: <tenure3 value>} ?
Seems like you're looking for this aggregation update:
db.collection.update({},
[
{
$set: {
experience: {
$concatArrays: [
"$experience",
[
{
company: "$company3",
title: "$title3",
tenure: "$tenure3"
}
]
]
}
}
},
{
$unset: "company3"
},
{
$unset: "tenure3"
},
{
$unset: "title3"
}
],
{
multi: true
})
Playground: https://mongoplayground.net/p/xoEveE0rdBN
I need to modify a document inside an array that is inside another array.
I know MongoDB doesn't support multiple '$' to iterate on multiple arrays at the same time, but they introduced arrayFilters for that.
See: https://jira.mongodb.org/browse/SERVER-831
MongoDB's sample code:
db.coll.update({}, {$set: {“a.$[i].c.$[j].d”: 2}}, {arrayFilters: [{“i.b”: 0}, {“j.d”: 0}]})
Input: {a: [{b: 0, c: [{d: 0}, {d: 1}]}, {b: 1, c: [{d: 0}, {d: 1}]}]}
Output: {a: [{b: 0, c: [{d: 2}, {d: 1}]}, {b: 1, c: [{d: 0}, {d: 1}]}]}
Here's how the documents are set:
{
"_id" : ObjectId("5a05a8b7e0ce3444f8ec5bd7"),
"name" : "support",
"contactTypes" : {
"nonWorkingHours" : [],
"workingHours" : []
},
"workingDays" : [],
"people" : [
{
"enabled" : true,
"level" : "1",
"name" : "Someone",
"_id" : ObjectId("5a05a8c3e0ce3444f8ec5bd8"),
"contacts" : [
{
"_id" : ObjectId("5a05a8dee0ce3444f8ec5bda"),
"retries" : "1",
"priority" : "1",
"type" : "email",
"data" : "some.email#email.com"
}
]
}
],
"__v" : 0
}
Here's the schema:
const ContactSchema = new Schema({
data: String,
type: String,
priority: String,
retries: String
});
const PersonSchema = new Schema({
name: String,
level: String,
priority: String,
enabled: Boolean,
contacts: [ContactSchema]
});
const GroupSchema = new Schema({
name: String,
people: [PersonSchema],
workingHours: { start: String, end: String },
workingDays: [Number],
contactTypes: { workingHours: [String], nonWorkingHours: [String] }
});
I need to update a contact. This is what I tried using arrayFilters:
Group.update(
{},
{'$set': {'people.$[i].contacts.$[j].data': 'new data'}},
{arrayFilters: [
{'i._id': mongoose.Types.ObjectId(req.params.personId)},
{'j._id': mongoose.Types.ObjectId(req.params.contactId)}]},
function(err, doc) {
if (err) {
res.status(500).send(err);
}
res.send(doc);
}
);
The document is never updated and I get this response:
{
"ok": 0,
"n": 0,
"nModified": 0
}
What am I doing wrong?
So the arrayFilters option with positional filtered $[<identifier>] does actually work properly with the development release series since MongoDB 3.5.12 and also in the current release candidates for the MongoDB 3.6 series, where this will actually be officially released. The only problem is of course is that the "drivers" in use have not actually caught up to this yet.
Re-iterating the same content I have already placed on Updating a Nested Array with MongoDB:
NOTE Somewhat ironically, since this is specified in the "options" argument for .update() and like methods, the syntax is generally compatible with all recent release driver versions.
However this is not true of the mongo shell, since the way the method is implemented there ( "ironically for backward compatibility" ) the arrayFilters argument is not recognized and removed by an internal method that parses the options in order to deliver "backward compatibility" with prior MongoDB server versions and a "legacy" .update() API call syntax.
So if you want to use the command in the mongo shell or other "shell based" products ( notably Robo 3T ) you need a latest version from either the development branch or production release as of 3.6 or greater.
All this means is that the current "driver" implementation of .update() actually "removes" the necessary arguments with the definition of arrayFilters. For NodeJS this will be addressed in the 3.x release series of the driver, and of course "mongoose" will then likely take some time after that release to implement it's own dependencies on the updated driver, which would then no longer "strip" such actions.
You can however still run this on a supported server instance, by dropping back to the basic "update command" syntax usage, since this bypassed the implemented driver method:
const mongoose = require('mongoose'),
Schema = mongoose.Schema,
ObjectId = mongoose.Types.ObjectId;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const uri = 'mongodb://localhost/test',
options = { useMongoClient: true };
const contactSchema = new Schema({
data: String,
type: String,
priority: String,
retries: String
});
const personSchema = new Schema({
name: String,
level: String,
priority: String,
enabled: Boolean,
contacts: [contactSchema]
});
const groupSchema = new Schema({
name: String,
people: [personSchema],
workingHours: { start: String, end: String },
workingDays: { type: [Number], default: undefined },
contactTypes: {
workingHours: { type: [String], default: undefined },
contactTypes: { type: [String], default: undefined }
}
});
const Group = mongoose.model('Group', groupSchema);
function log(data) {
console.log(JSON.stringify(data, undefined, 2))
}
(async function() {
try {
const conn = await mongoose.connect(uri,options);
// Clean data
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.remove() )
);
// Create sample
await Group.create({
name: "support",
people: [
{
"_id": ObjectId("5a05a8c3e0ce3444f8ec5bd8"),
"enabled": true,
"level": "1",
"name": "Someone",
"contacts": [
{
"type": "email",
"data": "adifferent.email#example.com"
},
{
"_id": ObjectId("5a05a8dee0ce3444f8ec5bda"),
"retries": "1",
"priority": "1",
"type": "email",
"data": "some.email#example.com"
}
]
}
]
});
let result = await conn.db.command({
"update": Group.collection.name,
"updates": [
{
"q": {},
"u": { "$set": { "people.$[i].contacts.$[j].data": "new data" } },
"multi": true,
"arrayFilters": [
{ "i._id": ObjectId("5a05a8c3e0ce3444f8ec5bd8") },
{ "j._id": ObjectId("5a05a8dee0ce3444f8ec5bda") }
]
}
]
});
log(result);
let group = await Group.findOne();
log(group);
} catch(e) {
console.error(e);
} finally {
mongoose.disconnect();
}
})()
Since that sends the "command" directly through to the server, we see the expected update does in fact take place:
Mongoose: groups.remove({}, {})
Mongoose: groups.insert({ name: 'support', _id: ObjectId("5a06557fb568aa0ad793c5e4"), people: [ { _id: ObjectId("5a05a8c3e0ce3444f8ec5bd8"), enabled: true, level: '1', name: 'Someone', contacts: [ { type: 'email', data: 'adifferent.email#example.com', _id: ObjectId("5a06557fb568aa0ad793c5e5") }, { _id: ObjectId("5a05a8dee0ce3444f8ec5bda"), retries: '1', priority: '1', type: 'email', data: 'some.email#example.com' } ] } ], __v: 0 })
{ n: 1,
nModified: 1,
opTime:
{ ts: Timestamp { _bsontype: 'Timestamp', low_: 3, high_: 1510364543 },
t: 24 },
electionId: 7fffffff0000000000000018,
ok: 1,
operationTime: Timestamp { _bsontype: 'Timestamp', low_: 3, high_: 1510364543 },
'$clusterTime':
{ clusterTime: Timestamp { _bsontype: 'Timestamp', low_: 3, high_: 1510364543 },
signature: { hash: [Object], keyId: 0 } } }
Mongoose: groups.findOne({}, { fields: {} })
{
"_id": "5a06557fb568aa0ad793c5e4",
"name": "support",
"__v": 0,
"people": [
{
"_id": "5a05a8c3e0ce3444f8ec5bd8",
"enabled": true,
"level": "1",
"name": "Someone",
"contacts": [
{
"type": "email",
"data": "adifferent.email#example.com",
"_id": "5a06557fb568aa0ad793c5e5"
},
{
"_id": "5a05a8dee0ce3444f8ec5bda",
"retries": "1",
"priority": "1",
"type": "email",
"data": "new data" // <-- updated here
}
]
}
]
}
So right "now"[1] the drivers available "off the shelf" don't actually implement .update() or it's other implementing counterparts in a way that is compatible with actually passing through the necessary arrayFilters argument. So if you are "playing with" a development series or release candiate server, then you really should be prepared to be working with the "bleeding edge" and unreleased drivers as well.
But you can actually do this as demonstrated in any driver, in the correct form where the command being issued is not going to be altered.
[1] As of writing on November 11th 2017 there is no "official" release of MongoDB or the supported drivers that actually implement this. Production usage should be based on official releases of the server and supported drivers only.
I had a similar use case. But my second level nested array doesn't have a key. While most examples out there showcase an example with arrays having a key like this:
{
"id": 1,
"items": [
{
"name": "Product 1",
"colors": ["yellow", "blue", "black"]
}
]
}
My use case is like this, without the key:
{
"colors": [
["yellow"],
["blue"],
["black"]
]
}
I managed to use the arrayfilters by ommiting the label of the first level of the array nest. Example document:
db.createCollection('ProductFlow')
db.ProductFlow.insertOne(
{
"steps": [
[
{
"actionType": "dispatch",
"payload": {
"vehicle": {
"name": "Livestock Truck",
"type": "road",
"thirdParty": true
}
}
},
{
"actionType": "dispatch",
"payload": {
"vehicle": {
"name": "Airplane",
"type": "air",
"thirdParty": true
}
}
}
],
[
{
"actionType": "store",
"payload": {
"company": "Company A",
"is_supplier": false
}
}
],
[
{
"actionType": "sell",
"payload": {
"reseller": "Company B",
"is_supplier": false
}
}
]
]
}
)
In my case, I want to:
Find all documents that have any steps with payload.vehicle.thirdParty=true and actionType=dispatch
Update the actions set payload.vehicle.thirdParty=true only for the actions that have actionType=dispatch.
My first approach was withour arrayfilters. But it would create the property payload.vehicle.thirdParty=true inside the steps with actionType store and sell.
The final query that updated the properties only inside the steps with actionType=dispatch:
Mongo Shell:
db.ProductFlow.updateMany(
{"steps": {"$elemMatch": {"$elemMatch": {"payload.vehicle.thirdParty": true, "actionType": "dispatch"}}}},
{"$set": {"steps.$[].$[i].payload.vehicle.thirdParty": false}},
{"arrayFilters": [ { "i.actionType": "dispatch" } ], multi: true}
)
PyMongo:
query = {
"steps": {"$elemMatch": {"$elemMatch": {"payload.vehicle.thirdParty": True, "actionType": "dispatch"}}}
}
update_statement = {
"$set": {
"steps.$[].$[i].payload.vehicle.thirdParty": False
}
}
array_filters = [
{ "i.actionType": "dispatch" }
]
NOTE that I'm omitting the label on the first array at the update statement steps.$[].$[i].payload.vehicle.thirdParty. Most examples out there will use both labels because their objects have a key for the array. I took me some time to figure that out.
I have already checked the following stackoverflow questions:
Mongoose delete array element in document and save
How to remove array element in mongodb?
Here is what I tried:
var User = require('../model/user_model.js');
router.put('/favoritemovies/:id', function(req, res){
console.log(req.params.id);
console.log(req.body.email);//I am able to console.log both value
User.update( {_id: req.body.email}, { $pullAll: { favoriteMovies: {id:req.params.id} } } },
});
My user model:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var userSchema = new Schema ({
id: ObjectId,
name: String,
password: String,
email: {type: String, unique: true},
favoriteMovies: []
});
module.exports = mongoose.model('users', userSchema);
This is the structure of my JSON object:
{
"_id": {
"$oid": "583893712f599701531b60bf"
},
"name": "",
"password": "",
"email": "",
"favoriteMovies": [
{
"vote_average": ,
"video": ,
"vote_count": ,
"popularity": ,
"backdrop_path": "",
"title": "",
"original_language": "",
"original_title": "",
"id": ,
"genre_ids": [],
"release_date": "",
"overview": "",
"adult": ,
"poster_path": "",
"_id": ""
}
I would like to delete one or more elements from the favoriteMovies array, if their ids' matches my id. I don't get any error message, but the the element don't get removed either.
What would be the proper request to achieve that?
$pullAll is used for removing multiple entries out an array, and takes an Array in its query.
You're only looking to remove one entry, so just use $pull.
{ $pull: { favoriteMovies: {id: req.params.id} } } }
It will work using pull, you're sure that the update condition is correct ?
{_id: req.body.email}
Regarding your example :
"_id": {
"$oid": "583893712f599701531b60bf"
},
It seems that you should use :
{email: req.body.email}
I found out the problem, I missed the callback function at the end of the query.
User.update( {_id: req.body.email}, { $pull: { favoriteMovies: { id: req.params.id} }
}, function(err, model){})
The example from docs about many-to-many relationship supposes that companies would be added after the person was already created.
However, what if the person data comes from server with a list of companies (companies' ids) already?
Is it possible to modify the example so that the following code (or smt. similar) would be possible:
// somewhere before we have a collection of companies defined like this:
// [{id: 1, name: 'ibm'}, {id: 2, name: 'apple'}]
// and than we do:
paul = new Person({
name: 'Paul',
jobs: [1, 2]
})
paul.get('jobs').at(0).get('name') // 'ibm'
When trying to achieve this the same way I'd do with one-to-many relations, I fail:
Companies = Backbone.Collection.extend({model: Company})
companies = new Companies([{id: 1, name: 'ibm'}, {id: 2, name: 'apple'}])
john = new Person({
name: 'John',
jobs: [1]
})
john.get('jobs').toJSON() // []
companies.get(1).get('employees').toJSON() // []
Here's the fiddle you can play with: http://jsfiddle.net/ymr5Z/
Your MAIN problem is that you are trying to add Jobs by ID. You never created any job object though let alone set their id! You only created the companies.
A better way to go about adding jobs is to have an addJob() function which you give a company ID (or company) and it creates the Job model for you.
Full Code to fit your example
and specifically:
var Person = Backbone.RelationalModel.extend({
relations: [{
type: 'HasMany',
key: 'jobs',
relatedModel: Job,
reverseRelation: {
key: 'person'
//includeInJSON: false //if you don't want to show person
}
}],
addJob: function (company) {
this.get('jobs').add(new Job({
company: company
}));
}
});
paul.addJob(1);
paul.addJob(2);
Works perfectly. You might also want to set an includeInJSON to false for your reverse relationship on the Job to exclude the person!
[{
"company": {
"id": 1,
"name": "ibm",
"employees": [null]
},
"person": {
"name": "Paul",
"jobs": [null, {
"company": {
"id": 2,
"name": "apple",
"employees": [null]
}
}]
}
}, {
"company": {
"id": 2,
"name": "apple",
"employees": [null]
},
"person": {
"name": "Paul",
"jobs": [{
"company": {
"id": 1,
"name": "ibm",
"employees": [null]
}
},
null]
}
}]