Loopback4 and MongoDB query documents with null or undefined field - loopbackjs

How do I query for document with fields that are null or undefined?
e.g.
{id:1, updatedAt: null}
{id:2}
I already tried this code to no avail. Returns 0 matches.
const whereBuilder = new WhereBuilder();
const where: Where = whereBuilder
.eq('updatedAt', null)
//.eq('updatedAt', false)
.build();
myRepository.findOne({
where: where,
});
Thanks for the Help!
Edit 1: Field declaration on model.
#property({
type: 'date',
})
updatedAt?: string;

In MongoDB Shell
using $type operator to match undefined value, see this.
// match `undefined`
db.TEST.findOne({ updatedAt: {'$type':6} });
// match `null`
db.TEST.findOne({ updatedAt: null });
In LB4
// match `undefined`
return await this.myRepository.find(
{
where: {
// !!!! "$" will be added automatically here. (explained below)
updatedAt: { 'type': 6 }
},
}
);
// match `null`
return await this.myRepository.find(
{
where: {
updatedAt: null
},
}
);
Why { 'type': 6 } will be converted into { '$type': 6 } ?
./node_modules/loopback-connector-mongodb/lib/mongodb.js line 918:
your where will be refactored in this function
MongoDB.prototype.buildWhere = function(modelName, where, options) {
...
and in line 1011, { 'type': 6 } will be converted into { '$type': 6 }
...
} else {
query[k] = {};
query[k]['$' + spec] = cond;
}
...
by the way, in line 1016, you can see null will converted as {$type: 10}
...
if (cond === null) {
// http://docs.mongodb.org/manual/reference/operator/query/type/
// Null: 10
query[k] = {$type: 10};
} else {
...

Related

AdonisJS exists Validator

I'm following the official [documentation] (https://legacy.adonisjs.com/docs/4.0/validator) && indicative, but I couldn't find anything to help me.
I want to validate if the given param exists on database.
So I tried:
app/Validators/myValidator
const { rule } = use('Validator')
get rules () {
return {
userId: 'required|integer|exists:MyDatabase.users,userId', <-- this is what isn't working
date: [
rule('date'),
rule('dateFormat', 'YYYY-MM-DD')
]
}
}
// Getting the data to be validated
get data () {
const params = this.ctx.params
const { userId, date } = params
return Object.assign({}, { userId }, { date })
}
It gives me the following error:
{
"error": {
"message": "select * from `MyDatabase`.`users`.`userId` where `undefined` = '2' limit 1 - ER_PARSE_ERROR: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '.`userId` where `undefined` = '2' limit 1' at line 1",
"name": "ErrorValidator",
"status": 40
}
}
How should I properly indicate that I want to compare MyDatabase.users.userid to the given parameter?
After a few hard try/error I stumbled upon the solution.
Just need to follow what is inside hooks.js and pass the values separated by comma, like so:
get rules () {
return {
userId: 'required|integer|exists:MyDatabase,MyTable,columntoCompare',
}
}

I am mocking two functions exactly the same way. In one case the mock value is returned and in another case the real function is called. Why?

I have a file that exports some functions:
function getNow() {
console.log('real now');
return dayjs();
}
function groupProducts(productInfos, now) {
console.log('real group');
return productInfos.reduce((groups, productInfo) => {
const groupKey = dayjs(productInfo.saleStartDate) > now ? dayjs(productInfo.saleStartDate).format('YYYY-MM-DD') : dayjs(now).format('YYYY-MM-DD');
let group = groups[groupKey];
if (!group) {
group = [];
// eslint-disable-next-line no-param-reassign
groups[groupKey] = group;
}
group.push(productInfo.itemId);
return groups;
}, {});
}
async function fetchProducts(client, productInfos, now) {
const products = [];
const groups = groupProducts(productInfos, now);
for (const [date, ids] of Object.entries(productQueryGroups)) {
// eslint-disable-next-line no-await-in-loop
const productBatch = await fetchResources(
client.queryProducts,
{
articleIds: ids,
timestamp: date,
},
);
products.push(...productBatch);
}
return products;
}
module.exports = {
test: {
getNow,
groupProducts,
fetchProducts,
},
};
I run my tests with:
package.json script
"testw": "npx ../node_modules/.bin/jest --watch",
cli command:
npm run testw -- filename
In this test I exercise groupProducts and mock getNow. The real getNow is never called and the test passes.
describe('groupProducts', () => {
it('groups productInfo ids into today or future date groups', () => {
// Arrange
const nowSpy = jest.spyOn(test, 'getNow').mockReturnValue(dayjs('2001-02-03T04:05:06.007Z'));
const expectedMap = {
'2001-02-03': ['Art-Past', 'Art-Today'],
'2002-12-31': ['Art-Future-1', 'Art-Future-2'],
'2003-12-31': ['Art-Other-Future'],
};
const productInfos = [{
itemId: 'Art-Past',
saleStartDate: '1999-01-01',
}, {
itemId: 'Art-Today',
saleStartDate: '2001-02-03',
}, {
itemId: 'Art-Future-1',
saleStartDate: '2002-12-31',
}, {
itemId: 'Art-Future-2',
saleStartDate: '2002-12-31',
}, {
itemId: 'Art-Other-Future',
saleStartDate: '2003-12-31',
}];
// Assert
const dateToIds = test.groupProductInfosByTodayOrFutureDate(productInfos, test.getNow());
// Expect
expect(dateToIds).toEqual(expectedMap);
// Restore
nowSpy.mockRestore();
});
});
In this test I exercise fetchProducts and mock groupProducts. The real groupProducts is called and the causes the test to fail.
describe('fetchProducts', () => {
it.only('calls fetchResources with the timestamp and date for every product query group', async () => {
// Arrange
const productQueryGroups = {
[test.PRICE_GROUPS.CURRENT]: ['Art-Past', 'Art-Today'],
[test.PRICE_GROUPS.FUTURE]: ['Art-Future-1', 'Art-Future-2', 'Art-Other-Future'],
};
const groupProductsSpy = jest.spyOn(test, 'groupProducts').mockReturnValue( productQueryGroups);
const fetchResourcesSpy = jest.spyOn(test, 'fetchResources').mockResolvedValue([]);
// Act
await test.fetchProducts();
// Expect
expect(test.fetchResources).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ articleIds: [productQueryGroups[test.PRICE_GROUPS.CURRENT]], timestamp: test.PRICE_GROUPS.CURRENT }));
// Restore
groupProductsSpy.mockRestore();
fetchResourcesSpy.mockRestore();
});
});
Error message
98 | function groupProducts(productInfos, now) {
> 99 | return productInfos.reduce((groups, productInfo) => {
| ^
100 | const groupKey = dayjs(productInfo.saleStartDate) > now ? dayjs(productInfo.saleStartDate).format('YYYY-MM-DD') : dayjs(now).format('YYYY-MM-DD');
101 |
102 | let group = groups[groupKey];
Why is the real groupProducts called? To me it looks completely analogous to the previous example.

How to delete duplicates of a List<MyDataModel> (Dart/Flutter)

I have a futurebuilder that builds the UI based on a List, it does the job, however I get duplicates due to the UI being built again and again whenever I navigate. My question is, is there a innate method in Dart that can remove duplicates from a list? I've tried this StackOverflow question however it doesn't work.
Here is my custom model:
class HomeList {
Widget navigateScreen;
String imagePath;
PatientInfo patientInfo;
HomeList({
this.navigateScreen,
this.imagePath = '',
this.patientInfo,
});
static List<HomeList> homeList = [];
}
Here is my function for the futureBuilder i'm getting the data from my cloud_firestore:
_getPatients() async {
if (didLoadpatients == 0) {
print('this is didloadpatients at start of func $didLoadpatients');
var document = await db
.collection('users')
.document(mUser.uid)
.collection('patients');
document.getDocuments().then((QuerySnapshot query) async {
query.documents.forEach((f) {
uids.add(f.data['uID']);
});
didLoadpatients++;
print('this is didloadpatients at end of func $didLoadpatients');
for (var i = 0; i < uids.length; i++) {
var userDocuments = await db.collection('users').document(uids[i]);
userDocuments.get().then((DocumentSnapshot doc) {
print(doc.data);
homeList.add(HomeList(
imagePath: 'assets/fitness_app/fitness_app.png',
patientInfo: new PatientInfo.fromFbase(doc.data)));
});
print(homeList);
}
});
} else
print('I am leaving the get patient function');
}
Future<bool> getData() async {
_getCurrentUser();
await Future.delayed(const Duration(milliseconds: 1500), () async {
_getPatients();
});
return true;
}
Any help would be appreciated thank you!
To remove duplicates you can use Set Data Structure instead of List.
Just use Set instead of List to get unique values only.
Before Adding you can Remove Element from model this will Work
dummymodel.removeWhere((m) => m.id == id);
dummymodel.add(dummymodel.fromJson(data));
To Remove Duplicates from Data Model simply use Set (Data structure),
Original List with Duplicate Entries:
List<MyDataModel> mList = [MyDataModel(1), MyDataModel(2), MyDataModel(1), MyDataModel(3)];
New List that removes duplicate Entries from your List<MyDataModel>:
List<MyDataModel> mNewList = list.toSet().toList();
Output:
The result will be like
MyDataModel(1), MyDataModel(2), MyDataModel(3)
To remove the duplicate elements from custom object list, you need to override == and hashcode methods in your POJO class and then add the items in Set and again convert set to list to remove duplicate objects. Below is the working code:-
class TrackPointList {
double latitude;
double longitude;
String eventName;
Time timeZone;
TrackPointList({
this.latitude,
this.longitude,
this.eventName,
this.timeZone,
});
#override
bool operator==(other) {
// Dart ensures that operator== isn't called with null
// if(other == null) {
// return false;
// }
if(other is! TrackPointList) {
return false;
}
// ignore: test_types_in_equals
return eventName == (other as TrackPointList).eventName;
}
int _hashCode;
#override
int get hashCode {
if(_hashCode == null) {
_hashCode = eventName.hashCode;
}
return _hashCode;
}
factory TrackPointList.fromJson(Map<String, dynamic> json) => TrackPointList(
latitude: json["latitude"].toDouble(),
longitude: json["longitude"].toDouble(),
eventName: json["eventName"],
timeZone: timeValues.map[json["timeZone"]],
);
Map<String, dynamic> toJson() => {
"latitude": latitude,
"longitude": longitude,
"eventName": eventName,
"timeZone": timeValues.reverse[timeZone],
};
}
Above is the POJO class. Now below is the method which helps you to filter the objects according to the eventName data member.
List<TrackPointList> getFilteredList(List<TrackPointList> list){
final existing = Set<TrackPointList>();
final unique = list
.where((trackingPoint) => existing.add(trackingPoint))
.toList();
return unique;
}
This will work definitely.
Please +1 if it helps you.
I've come up with quite a brute force solution. Instead of
_getPatients() async {
if (didLoadpatients == 0) {
print('this is didloadpatients at start of func $didLoadpatients');
var document = await db
.collection('users')
.document(mUser.uid)
.collection('patients');
document.getDocuments().then((QuerySnapshot query) async {
query.documents.forEach((f) {
uids.add(f.data['uID']);
});
didLoadpatients++;
print('this is didloadpatients at end of func $didLoadpatients');
for (var i = 0; i < uids.length; i++) {
var userDocuments = await db.collection('users').document(uids[i]);
userDocuments.get().then((DocumentSnapshot doc) {
print(doc.data);
homeList.add(HomeList(
imagePath: 'assets/fitness_app/fitness_app.png',
patientInfo: new PatientInfo.fromFbase(doc.data)));
});
print(homeList);
}
});
} else
print('I am leaving the get patient function');
}
I've done what #Jay Mungara says and clear my Set everytime my UI rebuilds:
_getPatients() async {
homeList.clear();
if (didLoadpatients == 0) {
print('this is didloadpatients at start of func $didLoadpatients');
var document = await db
.collection('users')
.document(mUser.uid)
.collection('patients');
document.getDocuments().then((QuerySnapshot query) async {
query.documents.forEach((f) {
uids.add(f.data['uID']);
});
didLoadpatients++;
print('this is didloadpatients at end of func $didLoadpatients');
for (var i = 0; i < uids.length; i++) {
var userDocuments = await db.collection('users').document(uids[i]);
userDocuments.get().then((DocumentSnapshot doc) {
print(doc.data);
homeList.add(HomeList(
imagePath: 'assets/fitness_app/fitness_app.png',
patientInfo: new PatientInfo.fromFbase(doc.data)));
});
print(homeList);
}
});
} else
print('I am leaving the get patient function');
}
Thank you for all your answers!
this is a small examples to remove duplicate element
removeDuplicate() {
List<dynamic> demoList = [
{"userId": 1, "id": 1, "name": "thappu1"},
{"userId": 2, "id": 2, "name": "appu"},
{"userId": 1, "id": 1, "name": "thappu1"},
{"userId": 2, "id": 2, "name": "appu"},
{"userId": 2, "id": 2, "name": "appu"},
{"userId": 2, "id": 2, "name": "appu"},
{"userId": 2, "id": 2, "name": "appu"},
];
var toRemove = {};
demoList.forEach((e) {
toRemove.putIfAbsent("$e", () => e);
});
print(toRemove.keys.toList());
}
output is
[{userId: 1, id: 1, name: thappu1}, {userId: 2, id: 2, name: appu}]

Flutter: Selected value doesn't display in the dropdown

I'm populating cities name from SQLite database and trying to display as a drop down list. I make it work by following a tutorial, but having a small issue. The selected value is not displayed in dropdown, it keep displaying default hint value. However, I was able to assign and retrieve correct selected value.
Here is my code:
cities.dart
class Cities {
int id;
String name;
Cities(this.id, this.name);
Cities.fromMap(Map<String, dynamic> json) {
this.id = json["id"];
this.name = json["name"];
}
Map<String, dynamic> toMap() => {
'id': null,
'name': name,
};
}
Function that retrieve and returns value from db:
Future<List<Cities>> getCitiesList() async {
Database db = await instance.database;
final citiesData = await db.query('cities');
if (citiesData.length == 0) return null;
List<Cities> citiesList = citiesData.map((item) {
return Cities.fromMap(item);
}).toList();
return citiesList;
}
The code which builds drop down, inside Widget build:
//these are defined above in the code
Cities _city;
final databaseHelper = DatabaseHelper.instance;
FutureBuilder<List<Cities>>(
future: databaseHelper.getCitiesList(),
builder: (BuildContext context, AsyncSnapshot<List<Cities>> snapshot) {
if (!snapshot.hasData) return CircularProgressIndicator();
return DropdownButton<Cities>(
items: snapshot.data
.map((city) => DropdownMenuItem<Cities>(
child: Text(city.name),
value: city,
))
.toList(),
onChanged: (Cities value) {
setState(() {
_city = value;
});
},
isExpanded: true,
// value: _city, //uncommenting this line breaks the layout
hint: Text('Select City'),
);
},
),
Error in the console:
'package:flutter/src/material/dropdown.dart': Failed assertion: line 620 pos 15: 'items == null || items.isEmpty || value == null || items.where((DropdownMenuItem<T> item) => item.value == value).length == 1': is not true.
Un-commenting this value: _city, add same error in display (displays error 8 times, instead of dropdown list).
Questions:
How can I fix this issue?
How can I set default value from the list? (which will be selected by default)
You can do it in simple way, just create a simple list of strings and pass that list to dropdown menu.
Here's how:
Update your getCitiesList() function:
Future<List<String>> getCitiesList() async {
Database db = await instance.database;
final citiesData = await db.query(tblCities);
if (citiesData.length == 0) return null;
return citiesData.map((Map<String, dynamic> row) {
return row["name"] as String;
}).toList();
}
Add this inside your form page:
//initialize these at top
List<String> _citiesList = <String>[];
String _city;
void _getCitiesList() async {
final List<String> _list = await databaseHelper.getCitiesList();
setState(() {
_citiesList = _list;
});
}
Call _getCitiesList(); inside initState().
Add this inside your build method:
DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: _city,
items: _citiesList.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String newValue) {
setState(() {
_city = newValue;
});
},
)),

Sencha Touch - List with Search-Field (XMLStore)

I have a external XML-file which I use to filling my list. This works great.
But now I want to filter(search) the XML-data with a search-field on top of the list.
My List looks like this:
ToolbarDemo.views.Beitrage = Ext.extend(Ext.List, {
title: "Beiträge",
iconCls: "btnbeitraege",
id: 'disclosurelist',
store: storeXML,
itemTpl: '<div class="contact"><img src="{bild}" width="96" height="52" border="0"/> {titel}</div>',
grouped: true,
onItemDisclosure: function(record, btn, index) {
Ext.Msg.alert('', '<video width="200" height="200" x-webkit-airplay="allow" poster="'+ record.get('bild') +'" controls="controls" id="video_player" style="" tabindex="0"><source src="'+ record.get('video') +'"></source></video>', Ext.emptyFn);
} });storeXML.load();
And my XML-input looks like this:
Ext.regModel('beitrag', {fields: ['datum', 'titel', 'video', 'bild']});
var storeXML = new Ext.data.Store({
model: 'beitrag',
sorters: [
{
property : 'Datum',
direction: 'DESC'
}],
getGroupString : function(record) {
var month = record.get('datum').split('-');
return month[2] + '.' + month[1] + '.' + month[0];
},
method: 'GET',
proxy: {
url: 'beitraege.xml',
type: 'ajax',
reader: {
type: 'xml',
record: 'beitrag',
root: 'beitraege'
},
}});
I know it's an old question, but I have managed to filter my list using a filter function in it's store. Here is how I did:
In my view I have a text field (xtype: 'searchfield').
In the controller for this view I have registered for 2 events by using the 'control' property
control: {
'searchfield': {
clearicontap: 'onSearchClearIconTap',
keyup: 'onSearchKeyUp'
}
}
onSearchKeyUp function looks like this (note: the field I'm going to filter is 'docName')
onSearchKeyUp: function(field)
{
var value = field.getValue(),
store = this.getMaster().getStore();
store.clearFilter();
if (value)
{
var searches = value.split(' '),
regexps = [],
i;
for (i = 0; i < searches.length; i++)
{
//if it is nothing, continue
if (!searches[i]) continue;
//if found, create a new regular expression which is case insenstive
regexps.push(new RegExp(searches[i], 'i'));
}
store.filter(function(record)
{
var matched = [];
//loop through each of the regular expressions
for (i = 0; i < regexps.length; i++)
{
var search = regexps[i],
didMatch = record.get('docName').match(search);
//if it matched the first or last name, push it into the matches array
matched.push(didMatch);
} //if nothing was found, return false (dont so in the store)
if (regexps.length > 1 && matched.indexOf(false) != -1) {
return false;
} else {
//else true true (show in the store)
return matched[0];
}
});
}
}
The 'onSearchClearIconTap' function instead is called when the user taps on the clear icon that is the 'X' included in the searchfield component, that clears the text, so the only thing we want to do is to reset the filter for our list:
onSearchClearIconTap: function()
{
this.getMaster().getStore().clearFilter();
}