Building JSON object in desired way with Laravel - arrays

Hi There Beste Collegues,
I've having an problem building my json output structure well.
What i want is the following output:
{
comments: {
data: {
created_at: "date",
other: "etc",
from: {
username: "navidos",
user_id: 1
}
}
}
}
But the way it is now build is:
{
data: {
description: "post description",
status: "public",
link: "http://www.bla.com",
created_at: "2015-01-23 00:00:00",
comments: [
{
text: "comment text 1",
date: "2015-01-23 18:30:00",
id: 1
},
{
text: "comment text 2",
date: "2015-01-23 18:35:00",
id: 2
},
{
text: "comment text 3",
date: "2015-01-23 19:00:00",
id: 3
}
],
user: {
user_id: 1,
username: "navid",
profile_picture: null,
bio: null
}
}
}
The output i get is almost fine but i want the comment section in this output like the first output. I've tried everything with array_merge and array push but can't figure out what i'm doing wrong here. Is there anyone who can help me out. I'm using Laravel 4 and doing my Relations with the Eloquent ORM.
So in my route i've now the following.
$post = Post::find($id);
$postComments = $post->comments;
$usersPost = $post->user;
return Response::json(
$data = array('data'=>$post)
);
I would be very grateful if someone can help me out with this. Tnx in advanced.

You could simply name your relationship to User in Comment from():
public function from(){
return $this->belongsTo('User');
}
Then eager load comments and from:
$post = Post::with('comments.from')->find($id);
This should result in something like this:
{
description: "post description",
status: "public",
link: "http://www.bla.com",
created_at: "2015-01-23 00:00:00",
comments: [
{
text: "comment text 1",
date: "2015-01-23 18:30:00",
id: 1,
from: {
user_id: 1,
username: "navid",
profile_picture: null,
bio: null
}
}
// next comment
]
}
If you want to hide some of these attributes in your JSON output you can add them to the $hidden array in the model:
class Comment extends Eloquent {
protected $hidden = ['id'];
}

What i currently have in my Post.php model is:
//An post belongs to an user
public function user(){
return $this->belongsTo('User')->select(array('user_id', 'username','profile_picture','bio'));
}
//An post has many comments
public function comments(){
return $this->hasMany('Comment')->select(array('comment as text','date','comment_id as id'));
}
and my Comment.php model looks like:
//An post belongs to an user
public function user(){
return $this->belongsTo('User')->select(array('user_id', 'username','profile_picture','bio'));
}
and my User.php model looks like:
public function from(){
return $this->hasMany('comments');
}
in my Route now i having the following like you suggest:
$post = Post::with('comments.from')->find($id);
but this throws an error:
Missing argument 1 for Illuminate\Database\Query\Builder::from()
is there something i'm not doing correct?

Related

Trouble Adding objects to MongoDB array [duplicate]

Basically I have a mongodb collection called 'people'
whose schema is as follows:
people: {
name: String,
friends: [{firstName: String, lastName: String}]
}
Now, I have a very basic express application that connects to the database and successfully creates 'people' with an empty friends array.
In a secondary place in the application, a form is in place to add friends. The form takes in firstName and lastName and then POSTs with the name field also for reference to the proper people object.
What I'm having a hard time doing is creating a new friend object and then "pushing" it into the friends array.
I know that when I do this via the mongo console I use the update function with $push as my second argument after the lookup criteria, but I can't seem to find the appropriate way to get mongoose to do this.
db.people.update({name: "John"}, {$push: {friends: {firstName: "Harry", lastName: "Potter"}}});
Assuming, var friend = { firstName: 'Harry', lastName: 'Potter' };
There are two options you have:
Update the model in-memory, and save (plain javascript array.push):
person.friends.push(friend);
person.save(done);
or
PersonModel.update(
{ _id: person._id },
{ $push: { friends: friend } },
done
);
I always try and go for the first option when possible, because it'll respect more of the benefits that mongoose gives you (hooks, validation, etc.).
However, if you are doing lots of concurrent writes, you will hit race conditions where you'll end up with nasty version errors to stop you from replacing the entire model each time and losing the previous friend you added. So only go to the latter when it's absolutely necessary.
The $push operator appends a specified value to an array.
{ $push: { <field1>: <value1>, ... } }
$push adds the array field with the value as its element.
Above answer fulfils all the requirements, but I got it working by doing the following
var objFriends = { fname:"fname",lname:"lname",surname:"surname" };
People.findOneAndUpdate(
{ _id: req.body.id },
{ $push: { friends: objFriends } },
function (error, success) {
if (error) {
console.log(error);
} else {
console.log(success);
}
});
)
Another way to push items into array using Mongoose is- $addToSet, if you want only unique items to be pushed into array. $push operator simply adds the object to array whether or not the object is already present, while $addToSet does that only if the object is not present in the array so as not to incorporate duplicacy.
PersonModel.update(
{ _id: person._id },
{ $addToSet: { friends: friend } }
);
This will look for the object you are adding to array. If found, does nothing. If not, adds it to the array.
References:
$addToSet
MongooseArray.prototype.addToSet()
Use $push to update document and insert new value inside an array.
find:
db.getCollection('noti').find({})
result for find:
{
"_id" : ObjectId("5bc061f05a4c0511a9252e88"),
"count" : 1.0,
"color" : "green",
"icon" : "circle",
"graph" : [
{
"date" : ISODate("2018-10-24T08:55:13.331Z"),
"count" : 2.0
}
],
"name" : "online visitor",
"read" : false,
"date" : ISODate("2018-10-12T08:57:20.853Z"),
"__v" : 0.0
}
update:
db.getCollection('noti').findOneAndUpdate(
{ _id: ObjectId("5bc061f05a4c0511a9252e88") },
{ $push: {
graph: {
"date" : ISODate("2018-10-24T08:55:13.331Z"),
"count" : 3.0
}
}
})
result for update:
{
"_id" : ObjectId("5bc061f05a4c0511a9252e88"),
"count" : 1.0,
"color" : "green",
"icon" : "circle",
"graph" : [
{
"date" : ISODate("2018-10-24T08:55:13.331Z"),
"count" : 2.0
},
{
"date" : ISODate("2018-10-24T08:55:13.331Z"),
"count" : 3.0
}
],
"name" : "online visitor",
"read" : false,
"date" : ISODate("2018-10-12T08:57:20.853Z"),
"__v" : 0.0
}
First I tried this code
const peopleSchema = new mongoose.Schema({
name: String,
friends: [
{
firstName: String,
lastName: String,
},
],
});
const People = mongoose.model("person", peopleSchema);
const first = new Note({
name: "Yash Salvi",
notes: [
{
firstName: "Johnny",
lastName: "Johnson",
},
],
});
first.save();
const friendNew = {
firstName: "Alice",
lastName: "Parker",
};
People.findOneAndUpdate(
{ name: "Yash Salvi" },
{ $push: { friends: friendNew } },
function (error, success) {
if (error) {
console.log(error);
} else {
console.log(success);
}
}
);
But I noticed that only first friend (i.e. Johhny Johnson) gets saved and the objective to push array element in existing array of "friends" doesn't seem to work as when I run the code , in database in only shows "First friend" and "friends" array has only one element !
So the simple solution is written below
const peopleSchema = new mongoose.Schema({
name: String,
friends: [
{
firstName: String,
lastName: String,
},
],
});
const People = mongoose.model("person", peopleSchema);
const first = new Note({
name: "Yash Salvi",
notes: [
{
firstName: "Johnny",
lastName: "Johnson",
},
],
});
first.save();
const friendNew = {
firstName: "Alice",
lastName: "Parker",
};
People.findOneAndUpdate(
{ name: "Yash Salvi" },
{ $push: { friends: friendNew } },
{ upsert: true }
);
Adding "{ upsert: true }" solved problem in my case and once code is saved and I run it , I see that "friends" array now has 2 elements !
The upsert = true option creates the object if it doesn't exist. default is set to false.
if it doesn't work use below snippet
People.findOneAndUpdate(
{ name: "Yash Salvi" },
{ $push: { friends: friendNew } },
).exec();
An easy way to do that is to use the following:
var John = people.findOne({name: "John"});
John.friends.push({firstName: "Harry", lastName: "Potter"});
John.save();
In my case, I did this
const eventId = event.id;
User.findByIdAndUpdate(id, { $push: { createdEvents: eventId } }).exec();
Push to nested field - use a dot notation
For anyone wondering how to push to a nested field when you have for example this Schema.
const UserModel = new mongoose.schema({
friends: {
bestFriends: [{ firstName: String, lastName: String }],
otherFriends: [{ firstName: String, lastName: String }]
}
});
You just use a dot notation, like this:
const updatedUser = await UserModel.update({_id: args._id}, {
$push: {
"friends.bestFriends": {firstName: "Ima", lastName: "Weiner"}
}
});
This is how you could push an item - official docs
const schema = Schema({ nums: [Number] });
const Model = mongoose.model('Test', schema);
const doc = await Model.create({ nums: [3, 4] });
doc.nums.push(5); // Add 5 to the end of the array
await doc.save();
// You can also pass an object with `$each` as the
// first parameter to use MongoDB's `$position`
doc.nums.push({
$each: [1, 2],
$position: 0
});
doc.nums;
// This is the my solution for this question.
// I want to add new object in worKingHours(array of objects) -->
workingHours: [
{
workingDate: Date,
entryTime: Date,
exitTime: Date,
},
],
// employeeRoutes.js
const express = require("express");
const router = express.Router();
const EmployeeController = require("../controllers/employeeController");
router
.route("/:id")
.put(EmployeeController.updateWorkingDay)
// employeeModel.js
const mongoose = require("mongoose");
const validator = require("validator");
const employeeSchema = new mongoose.Schema(
{
name: {
type: String,
required: [true, "Please enter your name"],
},
address: {
type: String,
required: [true, "Please enter your name"],
},
email: {
type: String,
unique: true,
lowercase: true,
required: [true, "Please enter your name"],
validate: [validator.isEmail, "Please provide a valid email"],
},
phone: {
type: String,
required: [true, "Please enter your name"],
},
joiningDate: {
type: Date,
required: [true, "Please Enter your joining date"],
},
workingHours: [
{
workingDate: Date,
entryTime: Date,
exitTime: Date,
},
],
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
const Employee = mongoose.model("Employee", employeeSchema);
module.exports = Employee;
// employeeContoller.js
/////////////////////////// SOLUTION IS BELOW ///////////////////////////////
// This is for adding another day, entry and exit time
exports.updateWorkingDay = async (req, res) => {
const doc = await Employee.findByIdAndUpdate(req.params.id, {
$push: {
workingHours: req.body,
},
});
res.status(200).json({
status: "true",
data: { doc },
});
};
https://www.youtube.com/watch?v=gtUPPO8Re98
I ran into this issue as well. My fix was to create a child schema. See below for an example for your models.
---- Person model
const mongoose = require('mongoose');
const SingleFriend = require('./SingleFriend');
const Schema = mongoose.Schema;
const productSchema = new Schema({
friends : [SingleFriend.schema]
});
module.exports = mongoose.model('Person', personSchema);
***Important: SingleFriend.schema -> make sure to use lowercase for schema
--- Child schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const SingleFriendSchema = new Schema({
Name: String
});
module.exports = mongoose.model('SingleFriend', SingleFriendSchema);

Count an array inside an array and then sum them MongoDB NodeJS

I have an object that has an array of page objects and each page object has an array of questions.
Ex object:
{
Id: 1,
UserId: 14,
Deleted: false,
Collaborators: [],
Title: "Awesome",
Pages: [{
Id: 1,
Title: 'Jank',
Questions: [
{ Id: 1, Content: 'Ask me about it' },
{ Id: 2, Content: 'Ask me about it again' }
]
}, {
Id: 2,
Title: 'Janker',
Questions: [
{ Id: 1, Content: 'Tell me about it' },
{ Id: 2, Content: 'Tell me about it again' }
]
}]
}
What I am trying to do is to get a count of all the questions for the entire bas object. I am not sure how to do that. I have tried to use aggregate and $sum the total questions and then do another function to $sum those all together to get a total for the entire object. Unfortunately my $sum is not working like I thought it would.
Ex code (nodejs):
var getQuestionCount = function(id) {
var cursor = mongo.collection('surveys').aggregate([{
$match: {
$or: [{
"UserId": id
}, {
"Collaborators": {
$in: [id]
}
}]
}
}, {
$match: {
"Deleted": false
}
}, {
$unwind: "$Pages"
},
{ $group: { _id: null, number: { $sum: "$Pages.Questions" } } }
], function(err, result) {
//This log just gives me [object Object], [object Object]
console.log('q count ' + result);
});
}
Any idea how to do this? My end result from the example object above would ideally return 4 as the question count for the whole object.
I'd try following shell query.
db.collection.aggregate([
// filter out unwanted documents.
{$match:{Id: 1}},
// Unwind Pages collection to access Questions array
{$unwind:"$Pages"},
// Count items in Questions array
{$project:{count: {$size:"$Pages.Questions"}}},
// Finally sum items previously counted.
{$group:{_id:"$_id", total: {$sum: "$count"}}}
])
Based on your sample document, it should return correct count of Questions.
{
"_id" : ObjectId("57723bb8c10c41c41ff4897c"),
"total" : NumberInt(4)
}

MongoDB query into an array of arrays

I have the following schema into my DB:
{ name: 'AFG',
documents: [
{ name: 'doc1',
templates: [
{ name: 'templ1',
lastModified: <Date>},
...]
},
...]
}
I am trying to make a query to look for the template with name 'templ1'. If I find it, I have to compare the last modification date with another and update the value. If I don't find it, I have to push it into the array.
I have tried to do it with the following query:
Country.findOne({name : countrySelected, documents : {$elemMatch: {name: documentSelected}}, 'documents.templates' : {$elemMatch: {name: templateSelected}}}, function(err, templateFound){...}
But I get the following error:
MongoError: cannot use the part (documents of documents.templates) to traverse the element ({documents: [ { name: "Passport", templates: [] }, { name: "Visa", templates: [] }, { name: "Driver's Licence", templates: [] }, { name: "Id Card", templates: [] }, { name: "Other", templates: [] } ]})
Someone knows how can I do it? Thanks!!
You have to use the $in operator to search in arrays.
Country.findOne({
name : countrySelected,
documents: {
$elemMatch: {
$and: [{
name: {
$in: documentSelected //<-- must be array like ['doc1']
}
}, {
'templates.name': {
$in: documentSelected //<-- must be array like ['tmpl1']
}
}]
}
}
}, function(err, templateFound){
//do something with err and templateFound
});
To update the LastModified date you need to update it in the callback function and save the document.
See this answer for nested array updates

Update a complex object on Backand using $http PUT

I am using Backand to provide the database and REST api for my Angular app.
I am working on a capability for users to make edits to a complex object, which should then be updated on the database. Straightforward enough...
The object looks a bit like this:
obj = {
id: 1, // assigned by the db
name: "My List",
tasks: [
{ id: 1, desc: "Task 1" },
{ id: 2, desc: "Task 2" },
...
]
}
For the update ($http PUT) call, I would like to use params: { deep: true } as a shortcut to minimise code and $http calls.
The problem at the moment is that while the PUT command updates the "master" object in the database, the edited "child" objects are not updated, but appended as new child objects.
For instance, if I try to update the master and child objects in one call:
$http({
method: 'PUT',
url: baseUrl + 'lists/' + list.id,
params: {
deep: true
},
data: {
id: 1,
name: "My To Do List",
tasks: [
{ id: 1, desc: "New Description for Task 1" },
{ id: 2, desc: "New Description for Task 2" }
]
}
}).then( .... );
the database doesn't update the child objects, it appends them. Here's how the resulting object is in the database:
list = {
id: 1,
name: "My To Do List", // Updated correctly
tasks: [
{ id: 1, desc: "Task 1" },
{ id: 2, desc: "Task 2" },
{ id: 3, desc: "New Description for task 1" }, // Added not updated
{ id: 4, desc: "New Description for task 2" } // Added not updated
]
}
I have made sure that the child objects' ids are correct.
Is there any way to do this succinctly or am I resigned to doing it in multiple stages? Does deep = true even work with PUT? The Backand docs don't mention it.
Backand identifies existing objects according to their
{
__metadata: {id: "6"}
}
When you "GET" an object from Backand it contains such a metadata.
When you "PUT" an object without the metadata id, Backand threats it as a new object.
So either use the same deep object that you originally got or add the metadata id.
$http({
method: 'PUT',
url: baseUrl + 'lists/' + list.id,
params: {
deep: true
},
data: {
"__metadata": { "id": "1" },
id: 1,
name: "My To Do List",
tasks: [
{ "__metadata": { "id": "1" }, id: 1, desc: "New Description for Task 1" },
{ "__metadata": { "id": "2" }, id: 2, desc: "New Description for Task 2" }
]
}
}).then( .... );
To delete tasks children in the "PUT" request you have to add overwrite=true to the params
params: {
deep: true,
overwrite: true
}

Node.JS + MongoDB aggregation Find array in array from DataBase in MongoDB

I have a Model in MongoDB which include an array(category) which has an array(picture) inside it.
Shirt Model looks like this:
{ _id: 100, category: [ { name: 'CatName', _id: 101, picture: [Object] } ] }
And my Picture Array looks like this:
[ { path: 'p1', _id: 102 },
{ path: 'p2', _id: 103 } ] }
I Pass 3 variables
main array id which is 100 (var1)
category name which is CatName (var2)
picture id which is 102 (var3)
I want to GET an array which looks like:
{ _id: 100, category: [ { name: 'CatName', _id: 101,
picture: [ { path: 'p1', _id: 102 }]
} ]
}
What I have tried is this:
Shirt.find( {_id: var1} ,
{category: { $and:[ { name: var2 } , { picture: {$elemMatch: {_id: var3}}} ] }} )
.exec(function(err, product) {
console.log('Result ' + product );
res.jsonp(product);
});
But the Result I receive is Undefined for First Code
Second Code That I tried:
Shirt.find( {_id: var1} ,
{category: {$elemMatch: { name: var2,picture: {$elemMatch: {_id: var3} }}} } )
And Result from second code filter the array for var1 and var2
But it contain the whole picture array which means it does not filter var3
What is the correct code to find what I want?
Is this a correct approach for a Shopping Website database Or you have a better suggestion?
Am I applying Parent and Child Database Correctly?
Thanks!
Following mongo query will give you expected result:
db.collection.find({
"category": {
$elemMatch: {
"name": "CatName",
"picture": {
$elemMatch: {
"_id": 102
}
}
}
}
})
You need to convert it in node.js format.
Following format may help you. Not Tested
collection.find({
_id: "" // add your match Id here} ,
{
category: {
"$elemMatch": {
"name": "CatName",
"picture": {
$elemMatch: {
"_id": 102
}
}
}
}
}
})
.exec(function(err, product) {
console.log('Result ' + product);
res.jsonp(product);
});
AFTER EDIT QUESTION
You should use mongo aggregation to get required values. Query will be like following -
db.collection.aggregate({
$unwind: '$category'
}, {
$unwind: '$category.picture'
}, {
$match: {
_id: 100, // add here your var1 variable instead of 100
'category.name': 'CatName', // add here your var2 variable instead of 'CatName'
'category._id': 102, //add your match id here if any otherwise remove this condition
'category.picture._id': 102 //add your var3 instead of 102
}
}, {
$group: {
_id: '$category.name',
'name': {
$first: '$category.name'
},
'picture': {
'$push': '$category.picture'
}
}
})

Resources