Conditionally populating a path in a Mongo query - mongodb

I have the following schema:
const connectionSchema = new Schema( {
...
event: {
type: Schema.ObjectId,
ref: 'events'
},
place: {
type: Schema.ObjectId,
ref: 'places'
},
} )
const eventsSchema = new Schema( {
...
place: {
type: Schema.ObjectId,
ref: 'places'
},
} )
When I query for either a single connection or a collection of connections, I want it to check if event is specified and if so return the place from the event, otherwise return the place from the connection.
I'm currently doing something like this when I'm querying a single connection:
let c
const execFunction = ( err, connection ) => {
if ( connection.event ) {
connection.place = connection.event.place
}
c = connection
}
const connectionPromise = Connection.findById( connectionId )
connectionPromise.populate( { path: 'event', populate: { path: 'place' } } )
connectionPromise.populate( 'place' )
connectionPromise.exec( execFunction )
But, I'm trying to avoid having to iterate through the entire collection in the execFunction to do the replace logic on each individual result if I query for a collection instead.
Is there a better (i.e. more performant) way to approach this?

As far as I know, there is no way to do conditional population with mongoose.
In your case, I think you are trying to do "deep population".
Deep population is not supported out of the box, but there is a plugin for that! mongoose-deep-populate
const connectionQuery = Connection.findById( connectionId )
connectionQuery.populate( 'place event event.place' )
connectionQuery.exec( execFunction );
I'm guessing that 'place event.place' will probably do it: 'event' should be implied.
I also expect that, if place or event or event.place are undefined, there won't be an error, they simply won't be populated.
But I don't think there is any way, in one line, to skip populating place when event.place is available.
By the way, if you want to use promises, you could write like this:
return Connection.findById( connectionId )
.populate( 'place event event.place' )
.then( onSuccessFunc );
to propagate errors, or:
Connection.findById( connectionId )
.populate( 'place event event.place' )
.then( onSuccessFunc )
.catch( onErrorFunc );
to handle errors locally. I find that preferable to callbacks, because we don't have to worry about if (err) at every stage.

Related

RTK query with Storybookjs

I am using RTK query with typescript in my react application and its working fine however storybookjs is not able to mock data for RTK query.
I am trying to mock store object as shown in this storybook document.
example -
export const Test = Template.bind({});
Test.decorators = [
(story) => <Mockstore data={myData}>{story()}</Mockstore>,
];
.
.
.
const customBaseQuery = (
args,
{ signal, dispatch, getState },
extraOptions
) => {
return { data: [] }; // <--- NOT SURE ABOUT THIS
};
const Mockstore = ({ myData, children }) => (
<Provider
store={configureStore({
reducer: {
[myApi.reducerPath]: createApi({
reducerPath: 'myApi',
baseQuery: customBaseQuery,
endpoints: (builder) => ({
getMyData: myData, //<-- my mock data
}),
}).reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(myApi.middleware),
})}
>
{children}
</Provider>
);
Since RTK query hook is autogenerated, I am not sure how to mock it in storybookjs. Instead of getting mock data storybook if trying to fetch actual data.
Please help me.
You'd do
endpoints: (builder) => ({
getMyData: builder.query({
queryFn: () => { data: myData }, //<-- my mock data
})
}),
alternatively you could leave the store setup just as it is and use msw to mock the real api.

Redux toolkit slice. Pure functions inside field reducers

Sup ya. Just wanna specify can we use it? I think we can just wanna be sure 100%
https://codesandbox.io/s/redux-toolkit-state-new-array-4sswi?file=/src/redux/slices/slice.js:111-425
const someSlice = createSlice({
name: "someSlice",
initialState: {
users: [],
status: ""
},
reducers: {
actionSuccess: (state, { payload }) => {
state.users = payload.users;
},
actionFailure: (state) => {
// can we use here function?
statusHandler(state)
}
}
});
A reducer should not trigger any side effects. That is, it should not do anything other than change the contents of the state. So you should not call a statusHandler which would trigger external effects.
If your statusHandler function does nothing but update the state, that does seem to work in my testing and I'm not aware of any reason why it shouldn't be okay.
Redux Toolkit uses Immer behind the scenes to deal with immutable updates, so this question basically boils down to whether these two functions, updatedInline and updatedExternally, are equivalent. They are, as far as I can tell.
const {produce} = immer;
const initialState = {
status: ''
};
const updatedInline = produce( initialState, draft => {
draft.status = 'failed';
})
const mutateStatus = (state) => {
state.status = 'failed';
}
const updatedExternally = produce( initialState, mutateStatus )
console.log("Updated Inline: \n", updatedInline);
console.log("Updated Externally: \n", updatedExternally);
<script src="https://cdn.jsdelivr.net/npm/immer#8.0.1/dist/immer.umd.production.min.js"></script>

the best way to exclude some data from a method in sails controller

I want's to exclude some data in some controller method and in other method i want's that data. I do it with forEach function right into method after finding that :
nine: function (req, res) {
Dore.find()
.limit(9)
.sort('createdAt DESC')
.populate('file')
.exec(function (err, sh) {
if (err) {
return res.negotiate(err);
} else {
console.log('before : ', sh);
sh.forEach(function (item, i) {
delete item.zaman;
});
console.log('after : ', sh);
return res.send(sh);
}
});
},
I want to know how possible to do that with finding and do not included ever in finding so we don't need to remove that again with forEach.
tanks
As #zabware say we have select method in Query option I try this format but do not work and return all data :
I try to use that with following format but don't working :
Model.find( {
where: {},
limit: 9,
sort: 'createdAt DESC'
},
{
select: [ 'id', 'createdAt' ]
} )
and
Model.find( {
where: {},
limit: 9,
sort: 'createdAt DESC',
select: [ 'id', 'createdAt' ]
} )
and
Model.find( {}, select: [ 'id', 'createdAt' ] )
Although toJson is really designed to solve the kind of problem you are facing, it will not help you, since in some actions you want to exclude certain fields and in others not.
So we need to select fields per query. Luckily there is a solution for this in waterline:
Dore.find({}, {select: ['foo', 'bar']})
.limit(9)
.sort('createdAt DESC')
.populate('file')
.exec(function (err, sh) {
console.log(err);
console.log(sh);
});
This is supported since sails 0.11 but not really documented. You can find it under query options here https://github.com/balderdashy/waterline-docs/blob/e7b9ecf2510646b7de69663f709175a186da10d5/queries/query-language.md#query-options
I accidentally stumbled upon it while playing with the debugger - Sails version 1.2.3
There is something like omit and You can put it into your find method call:
const user = await User.findOne({
where: {email : inputs.email},
omit: ['password']
}); //There won't be password field in the user object
It is a pity that there is no word about it in the documentation.

How to get last inserted record in Meteor mongo?

I want to get last inserted item in minimongo. currently i'm doing it in this way (code on client side):
addBook()
{
BookCollection.insert(
this.book , ( err , insertedBook_id )=>
{
this.book_saved = true;
console.group('Output:')
console.log(err);
console.log( insertedBook_id );
console.log(
BookCollection.find({_id: insertedBook_id})
.fetch()
);
console.groupEnd();
//this.router.navigate(['/book', insertedBook_id]);
}
);
}
Output in console:
undefined
t6Lwrv4od854tE7st
[]
As you can see, when i redirect to newly created page, i cant find a book, but in mongo shell i clearly see that record was added. Should i wait for some event like BookCollection.insert(this.book).then( . . . )? Please help me!)
When i go back to the 'all books page', i see new record and can click, all works normal, no errors.
In the /book controller:
ngOnInit()
{
this.sub = this.curr_route.params.subscribe(
params=>
{
this.book = BookCollection.findOne( params[ '_id' ] ); // .fetch() == []
//if(!this.book)
// this.router.navigate(['/books']);
Meteor.subscribe(
'book' , params[ '_id' ] , ()=>
{
this.book = BookCollection.findOne( params[ '_id' ] );
} , true
)
console.groupCollapsed( 'BookDetailsCardComponent log data:' )
console.group( 'Book:' );
console.log( this.book );
console.groupEnd();
console.groupEnd();
} , true
);
}
This is probably a timing issue. The _id is most likely created and returned immediately while the actual data is inserted into the database, which would explain why doing a find() on that _id right away comes back as an empty array.
Is there a reason you are doing your insert from the client rather than doing it on the server and using a Meteor method to initiate it? Changing to this approach might resolve your issue.

CKEditor Link dialog modification

I am trying to add a drop down to CKEditor's link dialog. When selected, the drop down should insert corresponding class name to the link.
CKEDITOR.on( 'dialogDefinition', function( ev ) {
var dialogName = ev.data.name;
var dialogDefinition = ev.data.definition;
if ( dialogName == 'link' ) {
var infoTab = dialogDefinition.getContents( 'info' );
infoTab.add({
type: 'select',
label: 'Display link as a button',
id: 'buttonType',
'default': '',
items: [
['- Not button -', ''],
['Button one', 'btn-primary'],
['Button two', 'btn-success'],
['Button three', 'btn-danger']
],
commit: function(data) {
data.className = this.getValue();
}
});
}
});
I have a feeling commit function is not doing the job, but cannot figure out how to make it work. I saw a code that almost does the same thing as I want at http://www.lxg.de/code/simplify-ckeditors-link-dialog. I tried it and it does not work either.
I am using CKEditor 4.3.2.
I appreciate your help in advance.
If you console.log the data object in link dialog's onOk, you'll find quite a different hierarchy. Element classes are in data.advanced.advCSSClasses. But even if you decide to override (or extend) the value of this property in your commit, your string will be nuked by the original commit of advCSSClasses input field ("Advanced" tab) anyway. So the approach got to be a little bit different:
Always store the value of the select in data.
Override commit of advCSSClasses input field to consider stored value.
Remember to execute the original commit of advCSSClasses input.
Here we go:
CKEDITOR.on( 'dialogDefinition', function( ev ) {
var dialogName = ev.data.name;
var dialogDefinition = ev.data.definition;
if ( dialogName == 'link' ) {
var infoTab = dialogDefinition.getContents( 'info' ),
advTab = dialogDefinition.getContents( 'advanced' ),
advCSSClasses = advTab.get( 'advCSSClasses' );
infoTab.add( {
type: 'select',
label: 'Display link as a button',
id: 'buttonType',
'default': '',
items: [
['- Not button -', ''],
['Button one', 'btn-primary'],
['Button two', 'btn-success'],
['Button three', 'btn-danger']
],
commit: function( data ) {
data.buttonType = this.getValue();
}
});
var orgAdvCSSClassesCommit = advCSSClasses.commit;
advCSSClasses.commit = function( data ) {
orgAdvCSSClassesCommit.apply( this, arguments );
if ( data.buttonType && data.advanced.advCSSClasses.indexOf( data.buttonType ) == -1 )
data.advanced.advCSSClasses += ' ' + data.buttonType;
};
}
});
Now you got to only write a setup function which will detect whether one of your button classes is present to set a proper value of your select field once the dialog is open.