What should I use other than .findAndModify? - meteor

I have the following code:
var roomDoc = Rooms.findAndModify({
query: {name: roomName},
update: {$setOnInsert: {unixTimestamp: unixTimestampSeconds()}},
new: true,
upsert: true
});
After getting an error that .findAndModify is undefined, I realized, Meteor doesn't implement .findAndModify.
Is there a Meteor way to achieve similar functionality by using different queries?

However the answer above explains this specific case, in many other cases it is supposed that findAndModify is an atomic operation that could be used for example to generate unique IDs:
http://docs.mongodb.org/v3.0/tutorial/create-an-auto-incrementing-field/
It seems that Meteor won't implement a wrapper of findAndModify as a corresponding ticket is already closed, however the community proposed several solutions that already work on both server and client sides:
https://github.com/meteor/meteor/issues/1070

You can do this directly, with slightly different syntax.
var roomDoc = Rooms.rawCollection().findAndModify(
{ name: name },
[],
{ $setOnInsert: {unixTimestamp: unixTimestampSeconds()} },
{ new: true, upsert: true }
)
More info here:
https://forums.meteor.com/t/automatically-increment-order-numbers/11261/12

Isn't the obvious thing just to do the find() and upsert() separately.
Rooms.upsert({name: roomName}, {$setOnInsert: {unixTimestamp: unixTimestampSeconds()}};
var roomDoc = Rooms.findOne({name: roomName});
This is probably slightly less efficient because of two db calls but if that bothers you just write your own meteor wrapper for Mongo's findAndModify() by using Rooms.rawCollection().

Related

Change a mutation value when fetching another query RTK Query?

I have a query that fetches all data, and also have a mutation that handles delete an item
const {
isFetching: isFetchingProducts,
data: productsData,
refetch
} = useGetProductsQuery("");
const [
deleteProduct,
{ isSuccess: productDeleted, error: deleteProductFailed }
] = useDeleteProductMutation();
I'm trying to make productDeleted false when isFetchingProducts is true, we could achieve that in old redux by for example
case(GET_PRODUCTS)
isFetching: true,
productDeleted: false
How to do that in the RTK query? thanks
Those two exist completely independently from each other, so there is no real way of doing that.
We will be adding a .reset functionality at some point in the future (github issue here), but currently you will just have to track something like that in a local component state variable.

How can I update deeply nested object inside array?

Hello good people of the stack!
I am working on a react-redux application and I am trying to update a property on a deeply nested structure in my reducer. The data structure is as follows and I want to update the text property:
state = {
assessment: {
requirements: [
questions: [
{
text
}
]
]
}
}
so I have tried the following:
// reducer code...
return {
...state,
[assessmentId]: {
...state[assessmentId],
requirements: [
...state[assessmentId].requirements,
[requirementId]: [
...state[assessmentId].requirements[requirementsId],
questions: [
...state[assessmentId].requirements[requirementsId].questions,
[questionId]: {
text: action.payload.response.text
},
],
] ,
],
},
};
This is more pseudo code than actual code to remove complexity.
I do not see any change in redux dev tools so I am wondering if I have made a mistake the way I get the nested objects and array elements.
I was also curious about using combine reducers here. I asked a colleague and they suggested to use that but I am unsure how you would take that approach here. As always, any help is appreciated.
I recommend immer for deep state changes in your reducers.
It adds a little weight to your bundle, and you'll get better performance from using the spread operator, but if you can live with that it'll make your code easier to read and write.
import produce from "immer";
// reducer code...
return produce(state, draft => {
draft[assessmentId].requirements[requirementsId].questions[questionsIndex].text = action.payload.response.text;
});
I'd say your issue stems from questions being an array which will take a little more work to keep straight than object based state.
As it is you appear to be trying to set the question value as if questions was an object. Maybe you just need to drop the [questionId] syntax, eg
questions: [
...state[assessmentId].requirements[requirementsId].questions,
{ text: action.payload.response.text },
],
This will set the text object as a new item on the end of the array though.
Depending on what you need to do (ie what already exists in the array and whether you are trying to add or update) you'll want to have a read of:
https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns#inserting-and-removing-items-in-arrays
https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns#updating-an-item-in-an-array

4 level subscription nesting in meteor

I am using meteor and this is my schema, each is a separate collection:
Courses has many lectures
Lectures have many questions
Questions have many answers
I want 1 page where I can display a given course's lectures, questions, and answers. I can display a course's lectures no problem but I have issues with displaying further nested items. I'd ideally like to have:
Lecture has courseId
Answer has lectureId (but not courseId)
Question has answerId (but not lectureId or courseId)
Is that wise or should I embed courseIds and lectureIds in all child components? This is my iron router, I tried to extend the same idea that worked with nesting lectures with questions but I hit a stumbling block with how to feed the subscriptions the lecturesId:
Router.route('/courses/:_id', {
name: 'CoursePage',
waitOn: function(){
return [
Meteor.subscribe('singleCourse', this.params._id),
Meteor.subscribe('lectures', this.params._id),
Meteor.subscribe('questions', this.params._id)
];
},
data: function() {
return Courses.findOne(this.params._id);
}
});
This is the subscriptions for the course page, again with my stumbling block of not really knowing how to feed in a lectureId:
Template.CoursePage.helpers({
Lectures: function() {
return Lectures.find({courseId: this._id});
},
Questions: function(lectureId) {
return Questions.find({courseId: this._id, lectureId: lectureId});
}
});
Can anyone recommend a good way to do this 4 level nesting for a single page? I think that I am missing something obvious but I can't quite find a good example with google searching.
Thanks!
You can Publish Composite package for this. See the following sample code and edit as per your collection schemas,
Meteor.publishComposite('singleCourse', function (courseId) {
return [{
find: function() {
return Courses.find({ id: courseId});
}
}, {
find: function() {
return Lectures.find({ courseId: courseId});
},
children: [{
find: function(lecture) {
return Questions.find({ lectureId: lecture.id });
},
children: [{
find: function(question) {
return Answers.find({ questionId: question.id });
}
}]
}}
}]
});
Then in your router, you can simply make one subscription call,
Router.route('/courses/:_id', {
name: 'CoursePage',
waitOn: function(){
return [
Meteor.subscribe('singleCourse', this.params._id)
];
},
data: function() {
return Courses.findOne(this.params._id);
}
});
This is one of the best packages (if not the best) as of now to reactively publish set of related documents from different collections.
There are some known issues while doing these kind of reactive joins but for smaller datasets, this works without any problem.
Hope it helps.
Mongo can support using aggregation. $lookup will let you connect and gather data between your collections like an SQL join.
Using this in meteor requires using an external mongo ($lookup is new as of Mongo 3.2, meteor's Mongo is still 2.6.7) and a package such as the meteorhacks:aggregate package. There are other packages that address this, as mentioned in the comments, aggregate is just what I've used; with it you call Courses.aggregate(...) per the mongo aggregation documentation to produce the data that you require.
In my use, I had a Meteor method defined that took filter parameters as arguments
'aggregateReport':function(filterPersonnel, filterCourse, filterQuarter){
return Personnel.aggregate([{$match: filterPersonnel}, {$unwind: "$courses"},
{$lookup: {from: "courses", localField: "courses", foreignField: "_id",
as: "course_docs"}}, {$unwind: "$course_docs"}, {$match: filterCourse},
{$match: filterQuarter}]);
The Personnel have: country, course date, lastname, fullname, ..., course #, course. (The ellipses covers non-relevant to the query). The above queries Personnel per the filter, spools it out to one record per course (this is a transcript type of view for many people in a program), then adds the information from Courses as course_docs to the returned Personnel, and then filters by course parameters and date parameters. code and dependencies were meteor 1.2; Feb 2016

Meteor - How do you exclude properties in a child collection from being published?

Imagine you have a collection similar to the following...
Tests = [
{
name: 'Some Test',
questions: [
{ question: 'Answer to life, the universe, and everything?', answer: '42' },
{ question: 'What is your favorite color?', answer: 'Blue' },
{ question: 'Airspeed velocity of unladen European Swallow?', answer: '24 mph' }
]
}
];
How do you publish the entire collection except for the answer property?
I understand you can do the following to omit properties from the publish...
Meteor.publish('tests', function() {
return Tests.find({}, {fields: {name:0}});
});
But I'm not sure how to omit a property from an array property.
Thanks!
It can't be done the way you want to do it. Meteor only supports field specifiers that are 1 level deep. You can sometimes get a sub-field specifier to work, but it's not reliable.
You can put your questions into their own collection with a testId field that links them back to the test, relational style. One question per document, and then you'll be able to specify that only the question field gets published.
Meteor.publish ('questions', function(testId) {
return Questions.find({testId: testId}, {fields: {question: 1}})
});
It's not ideal, but pretty painless compared to trying to find a workaround that allows your questions to live in the test document.
There might be a way to do this manually with a more involved publish. There's a similar question here with an answer that gets into it.

How to work with async code in Mongoose virtual properties?

I'm trying to work with associating documents in different collections (not embedded documents) and while there is an issue for that in Mongooose, I'm trying to work around it now by lazy loading the associated document with a virtual property as documented on the Mongoose website.
The problem is that the getter for a virtual takes a function as an argument and uses the return value for the virtual property. This is great when the virtual doesn't require any async calls to calculate it's value, but doesn't work when I need to make an async call to load the other document. Here's the sample code I'm working with:
TransactionSchema.virtual('notebook')
.get( function() { // <-- the return value of this function is used as the property value
Notebook.findById(this.notebookId, function(err, notebook) {
return notebook; // I can't use this value, since the outer function returns before we get to this code
})
// undefined is returned here as the properties value
});
This doesn't work since the function returns before the async call is finished. Is there a way I could use a flow control library to make this work, or could I modify the first function so that I pass the findById call to the getter instead of an anonymous function?
You can define a virtual method, for which you can define a callback.
Using your example:
TransactionSchema.method('getNotebook', function(cb) {
Notebook.findById(this.notebookId, function(err, notebook) {
cb(notebook);
})
});
And while the sole commenter appears to be one of those pedantic types, you also should not be afraid of embedding documents. Its one of mongos strong points from what I understand.
One uses the above code like so:
instance.getNotebook(function(nootebook){
// hey man, I have my notebook and stuff
});
While this addresses the broader problem rather than the specific question, I still thought it was worth submitting:
You can easily load an associated document from another collection (having a nearly identical result as defining a virtual) by using Mongoose's query populate function. Using the above example, this requires specifying the ref of the ObjectID in the Transaction schema (to point to the Notebook collection), then calling populate(NotebookId) while constructing the query. The linked Mongoose documentation addresses this pretty thoroughly.
I'm not familiar with Mongoose's history, but I'm guessing populate did not exist when these earlier answers were submitted.
Josh's approach works great for single document look-ups, but my situation was a little more complex. I needed to do a look-up on a nested property for an entire array of objects. For example, my model looked more like this:
var TransactionSchema = new Schema({
...
, notebooks: {type: [Notebook]}
});
var NotebookSchema = new Schema({
...
, authorName: String // this should not necessarily persist to db because it may get stale
, authorId: String
});
var AuthorSchema = new Schema({
firstName: String
, lastName: String
});
Then, in my application code (I'm using Express), when I get a Transaction, I want all of the notebooks with author last name's:
...
TransactionSchema.findById(someTransactionId, function(err, trans) {
...
if (trans) {
var authorIds = trans.notebooks.map(function(tx) {
return notebook.authorId;
});
Author.find({_id: {$in: authorIds}, [], function(err2, authors) {
for (var a in authors) {
for (var n in trans.notebooks {
if (authors[a].id == trans.notebooks[n].authorId) {
trans.notebooks[n].authorLastName = authors[a].lastName;
break;
}
}
}
...
});
This seems wildly inefficient and hacky, but I could not figure out another way to accomplish this. Lastly, I am new to node.js, mongoose, and stackoverflow so forgive me if this is not the most appropriate place to extend this discussion. It's just that Josh's solution was the most helpful in my eventual "solution."
As this is an old question, I figured it might use an update.
To achieve asynchronous virtual fields, you can use mongoose-fill, as stated in mongoose's github issue: https://github.com/Automattic/mongoose/issues/1894

Resources