With Dexie, how to remove a value from an array field for all objects in the table? - updates

From this question I can find all objects in my table where a value occurs in a field that is an array. Now I need to delete that value from that field, for all objects in that table.
For example, suppose an events table, with objects:
{ people: ['John', 'Bob', 'Sue'] }
{ people: ['Harry', 'Sue', 'Jim'] }
{ people: ['John', 'Bob', 'Elaine'] }
{ people: ['Jim', 'Bob', 'Sue'] }
Suppose I want to delete 'Sue' from the people field for all objects.
How is this done with Dexie?

Adding the following code in an async function would do it:
await db.events.where('people').equals('Sue').modify(x => {
// This callback is run for every match.
// Here you can modify the people property to remove Sue from it:
x.people = x.people.filter(p => p !== 'Sue');
});
Note: Assume the schema is indexing 'people' with multiEntry index:
const db = new Dexie("testdb");
db.version(3).stores({
events: 'id, *people'
});
References:
https://dexie.org/docs/Collection/Collection.modify()
https://dexie.org/docs/MultiEntry-Index

Related

Connecting 3 separate tables with a single through table in Sequelize

I'm trying to figure out why this is not working as intended. Any insights would be appreciated. Here's my situation:
I have a legacy connection of two tables with a through table. Something like:
Product model
const Product = sequelize.define('Product', {
// model attributes
});
Product.associate = (models) => {
Product.belongsToMany(models.Client, { through: models.ProductOwner, as: 'Owners'});
}
Client model
const Client = sequelize.define('Client', {
// model attributes
});
Client.associate = (models) => {
Client.belongsToMany(models.Product, { through: models.ProductOwner, as: 'OwnedProducts'});
}
ProductOwner
const ProductOwner = sequelize.define('Product', {
// no attributes
});
These form the N:M association with the through table ProductOwner.
This allows me, for example, to easily add a client to a product or getting all products that an existing client owns.
product.addOwner(client);
client.getOwnedProducts();
Now, I have the need to establish another chain of ownership to products that is unrelated to client. However, since this is still ownership, I would like to use the ProductOwner through table. So I add the new model:
Company model
const Company = sequelize.define('Company', {
// model attributes
});
Company.associate = (models) => {
Company.belongsToMany(models.Product, { through: models.ProductOwner, as: 'OwnedProducts'})
}
And the new association to the Products model. I also write a migration to add the CompanyId to the ProductOwners table and verify that the new reference is built into the database.
Product model
const Product = sequelize.define('Product', {
// model attributes
});
Product.associate = (models) => {
Product.belongsToMany(models.Client, { through: models.ProductOwner, as: 'Owners'});
Product.belongsToMany(models.Company, {
through: models.ProductOwner, as: 'CompanyOwners'
});
}
Now, on my code, I should be able to write:
product.addCompanyOwner(company);
company.getOwnedProducts();
And indeed, using the product instance method to add a new company does not throw any errors. However, the CompanyId column in the ProductOwners through table is still NULL.
Logging the query generated by Sequelize I see that the references to ProductId and ClientId are there, but there is no mention of CompanyId. Looks as if it is not recognizing that a new reference exists from Sequelize's point of view. However, the instance methods do work...
Which brings me to the question of why do they work? I assume that, by working, Sequelize is indeed creating the associations. But if that is the case, then why does the value for CompanyId is not set with the query?
Even writing it explicitly does not produce the expect result of setting CompanyId...
db.ProductOwner.create({
ProductId: 1,
ClientId: 1
}) // works to set all values
db.ProductOwner.create({
ProductId: 1,
CompanyId: 1
}) // sets ProductId to 1, but CompanyId is still NULL
What am I missing?

How to override whole json document in mongodb update call

I must admit I am not exactly sure what is going on inside of the mongodbclient function update. In my code I currently have this:
app.post('/update', function(req, res) {
const params = req.body;
const newData = {
id: params.id,
data: params.data
a ton more fields will go here
};
db.collection('datas').update({id : params.id},
{ $set: {data : newData.data}}, (err, data) => {
if (!err) {
res.send({err: false});
} else {
res.send({err: true});
}
});
However I dont have the slightest idea what the whole $set bracket is doing or how I could modify it to override the entire document once there are a ton more fields on the incoming data. I suppose the id field really wont need to be updated but you can get the idea.
What is that bracket doing? And how can I customize it for my needs?
If you would like to override the entire document you can remove the $set tag.
$set simply sets the fields that you pass into it.
If you do not pass a $set or $unset command it will assume you are trying to overwrite the entire document.
Take the following document for example
{
_id: 1,
a: 1,
b: 2,
c: 3
}
Using set such as:
db.collection.update(
{_id: 1},
{$set: {
a:2,
b:4,
new: 123
}}
);
would return the following:
{
_id: 1,
a: 2,
b: 4,
c: 3,
new: 123
}
It updates a and b that were passed in but does not alter c as it was not passed in
using $unset will remove a field
db.collection.update(
{_id: 1},
{$unset: {
new: true
}}
);
would then return:
{
_id: 1,
a: 2
b: 4,
c: 3
}
Without passing in $set or $unset you can alter an entire document
db.collection.update(
{_id: 1},
{
comment: "what have I done to my data?!?!?!"
}
);
This will return, keeping _id unchanged but overwriting the entire document in the process
{
_id: 1,
comment: "what have I done to my data?!?!?!"
}
$set expects an hash. newData is already an hash, and it's the hash you need, so try:
$set: newData
Instead of
$set: {data: newData.data}
It should work.

node.js - if statement not working as expected

This piece of node.js code is run against a Spark History Server API.
What its supposed to do is find any jobs where the name matches the value passed in by uuid and return the id for only that job.
What the below code actually does is if the uuid is found in any job name, the id for every job is returned.
I think this has something to do with the way I'm parsing the JSON but I'm not entirely sure.
How do I change this so it works as I would like it to?
var arrFound = Object.keys(json).filter(function(key) {
console.log("gel json[key].name" + json[key].name);
return json[key].name;
}).reduce(function(obj, key){
if (json[key].name.indexOf(uuid)) {
obj = json[key].id;
return obj;
}
reduce is the wrong method for that. Use find or filter. You can even do that in the filter callback that you already have. And then you can chain a map to that to get the id property values for each matched key:
var arrFound = Object.keys(json).filter(function(key) {
console.log("gel json[key].name " + json[key].name);
return json[key].name && json[key].name.includes(uuid);
}).map(function(key) {
return json[key].id;
});
console.log (arrFound); // array of matched id values
Note also that your use of indexOf is wrong. You need to compare that value with -1 (not found). But nowadays you can use includes which returns a boolean.
Note that with Object.values you list the objects instead of the keys, which is more interesting in your case:
var arrFound = Object.values(json).filter(function(obj) {
console.log("gel obj.name " + obj.name);
return obj.name && obj.name.includes(uuid);
}).map(function(obj) {
return obj.id;
});
console.log (arrFound); // array of matched id values
While the accepted answer provides working code, I feel it's worth pointing out that reduce is a good way to solve this problem, and to me makes more sense than chaining filter and map:
const jobs = {
1: {
id: 1,
name: 'job: 2a2912c5-9ec8-4ead-9a8f-724ab44fc9c7'
},
2: {
id: 2,
name: 'job: 30ea8ab2-ae3f-4427-8e44-5090d064d58d'
},
3: {
id: 3,
name: 'job: 5f8abe54-8417-4b3c-90f1-a7f4aad67cfb'
},
4: {
id: 4,
name: 'job: 30ea8ab2-ae3f-4427-8e44-5090d064d58d'
}
}
const matchUUID = uuid =>
(acc, job) => job.name.includes(uuid) ? [ ...acc, job.id ] : acc
const target = '30ea8ab2-ae3f-4427-8e44-5090d064d58d'
const matchTarget = matchUUID(target)
// [ 2, 4 ]
console.log(Object.values(jobs).reduce(matchTarget, []))
reduce is appropriate for these kinds of problems: taking a larger, more complex or complete value, and reducing it to the data you require. On large datasets, it could also be more efficient since you only need to traverse the collection once.
If you're Node version-constrained or don't want to use array spread, here's a slightly more 'traditional' version:
var result = Object.keys(jobs).reduce(
function (acc, key) {
if (jobs[key].name.includes(uuid)) {
acc.push(jobs[key].id)
}
return acc
},
[]
)
Note use of Object.keys, since Object.values is ES2017 and may not always be available. String.prototype.includes is ES2015, but you could always use indexOf if necessary.

Redux: display single record but json is an array

My react-redux app is getting a single record in JSON but the record is an array and therefore it looks like this (notice [ ] brackets):
{"person":[{"PersonID":1,"Name":"John Smith","Gender":0}]}
So, the redux store shows it as person->0->{"PersonID":1,"Name":"John Smith","Gender":0}. As such, the state shows that the person object is empty:
Name: this.props.person?this.props.person.Name:'object is empty',
My PersonPage.js includes the details page like this:
<PersonDetail person={this.props.person} />
The details page has this:
import React from 'react';
import classnames from 'classnames';
class PersonDetail extends React.Component {
state = {
Name: this.props.person?this.props.person.Name:'',
PersonID: this.props.person?this.props.person.PersonID:null,
loading: false,
done: false
}
componentWillReceiveProps = (nextProps) => {
this.setState({
PersonID: nextProps.person.PersonID,
Name: nextProps.person.Name
});
}
This is my raw Redux state:
people: [
[
{
PersonID: 51,
Name: 'John Smith',
Gender: 0
}
]
]
Person is an array, that contains the object in which Name key is present, so you need to use index also, write it like this:
this.props.person && this.props.person.length ? this.props.person[0].Name : '';
Check this example:
var data = {
"person":[
{
"PersonID":1,
"Name":"John Smith",
"Gender":0
}
]
};
console.log('Name: ', data.person[0].Name);
I think that you are supposed to map the person detail foreach person's data.
on the PersonPage.js ,
map it as follows:
{
this.props.person.map((p)=>{
return (<PersonDetail person={p} />)
})
}
If I was you I would make an util function like this:
const parsePeople = people => {
if (people instanceof Array) return parsePeople(people.pop())
return people
}
const people = [
[{
PersonID: 51,
Name: 'John Smith',
Gender: 0
}]
]
const person = parsePeople(people)
console.log(person) -> Object
Using recursion we check if people is an instance of Array, we call the function again using people.pop() which return the last element of the array.
you have an array on your person data... you can only access that without the 0 using map...
example:
componentWillReceiveProps = (nextProps) => {
var PersonID = nextProps.person ? nextProps.person.map(item => { return item.PersonID}) : '';
var Name = nextProps.person ? nextProps.person.map(item => { return item.Name}) : '';
this.setState({
PersonID,
Name
});
}
this is considering you only have 1 array on person.
I fixed it! It was a combination of two of the answers given:
In the PersonPage.js, I had to call the PersonDetails object like this:
<PersonDetail
person={this.props.person[0]}
/>
And this is the new MapStatetoProps:
function mapStateToProps(state, props) {
const { match } = props;
if (match.params.PersonID) {
return {
person: state.people
}
}
Thanks to those who answered. This drove me nuts.

Get only values from rows and associations with Sequelize

I am using Sequelize, MySQL and Node to write a web application.
For most of my DB needs, I usually do some verification, then fetch my models (eagerly with associations) and send them back to the client, almost always as-is (at least, up to now).
I wrote a little utility function getValuesFromRows to extract the values from a returned row array:
getValuesFromRows: function(rows, valuesProp) {
// get POD (plain old data) values
valuesProp = valuesProp || 'values';
if (rows instanceof Array) {
var allValues = [];
for (var i = 0; i < rows.length; ++i) {
allValues[i] = rows[i][valuesProp];
}
return allValues;
}
else if (rows) {
// only one object
return rows[valuesProp];
}
return null;
}
// ...
...findAll(...)...complete(function(err, rows) {
var allValues = getValuesFromRows(rows);
sendToClient(errToString(err, user), allValues);
});
However, I am adding more and more complex relations to my DB models. As a result, I get more associations that I have to fetch. Now, I don't only have to call above function to get the values from each row, but also I need more complicated utilities to get the values from all included (eagerly loaded) associations. Is there a way to only get values from Sequelize queries (and not the Sequelize model instance) that also includes all associated values from the instance?
Else, I would have to manually "get all values from each Project and add one item to that values object for the values property of each entry of Project.members" (for example). Note that things get worse fast if you nest associations (e.g. members have tasks and tasks have this and that etc.).
I am guessing that I have to write such utility myself?
I found a simple solution to my problem, by extending my existing POD converting function above with a recursion into all include'd associations of the query. The Solution works with find, findAll, all and possibly other operations with non-trivial results.
Code
/**
* Get POD (plain old data) values from Sequelize results.
*
* #param rows The result object or array from a Sequelize query's `success` or `complete` operation.
* #param associations The `include` parameter of the Sequelize query.
*/
getValuesFromRows: function(rows, associations) {
// get POD (plain old data) values
var values;
if (rows instanceof Array) {
// call this method on every element of the given array of rows
values = [];
for (var i = 0; i < rows.length; ++i) {
// recurse
values[i] = this.getValuesFromRows(rows[i], associations);
}
}
else if (rows) {
// only one row
values = rows.dataValues;
// get values from associated rows
if (values && associations) {
for (var i = 0; i < associations.length; ++i) {
var association = associations[i];
var propName = association.as;
// recurse
values[propName] = this.getValuesFromRows(values[propName], association.include);
};
}
}
return values;
}
Example
var postAssociations = [
// poster association
{
model: User,
as: 'author'
},
// comments association
{
model: Comment,
as: 'comments',
include: [
{
// author association
model: User,
as: 'author'
}
]
}
];
// ...
var query = {
where: ...
include: postAssociations;
};
// query post data from DB
return Post.findAll(query)
// convert Sequelize result to POD
.then(function(posts) {
return getValuesFromRows(posts, postAssociations);
})
// send POD back to client
.then(client.sendPosts);
In the above example, client.sendPosts receives an array. Each entry of the array will have properties author and comments. Each comment in the comments array will also have an author property. The entire array only contains POD (plain old data).