Sequelize update a record and it's associations - mysql

I have a model City which hasMany CitiesDescriptions.
I would like to update City, but whenever the data contains CitiesDescriptions I would like them to be updated as well.
This is what my data looks like:
{
"id": 4263,
"name": "Accra",
"country_id": 9,
"slug": "accra-5",
"code": "ACD",
"hdo_code": "",
"tourico_code": null,
"active": true,
"CitiesDescriptions": [
{
"id": 1,
"lang": "es",
"description": "text text"
}
]
}
So, in this case I want the data within CitiesDescriptions to be updated.
In the case that one of the objects inside CitiesDescriptions is a new record (doesn't have ID) I want it to be created.
Anyway, I haven't found a way to do this, whatever I found fails in some way. The closest I found that almost works is this:
var city = models.City.build(params, {
isNewRecord : false,
include : [models.CitiesDescription]
});
Promise.all([
city.save(),
city.CitiesDescriptions.map(function (description) {
if (description.getDataValue('id')) {
return description.save();
} else {
description.city_id = city.id;
return models.CitiesDescription.create(description.dataValues);
}
})
]).then(function(results) {
res.send(results);
});
This works except with the data doesn't have a CitiesDescriptions key.
Still, looks way to complicated to do, specially if tomorrow I have more associated models.
Isn't there any way to do this without so much hassle?
EDIT:
Couldnt find a solution, I made this and it works as intended.
var params = req.body;
var promises = [];
promises.push(models.City.update(params, {
where: {
id: parseInt(req.params.id)
}
}));
if(params.CitiesDescriptions) {
promises.push(
params.CitiesDescriptions.map(function (description) {
return models.CitiesDescription.upsert(description, {individualHooks: true});
})
);
}
Promise.all(promises).then(function(results) {
res.send(results);
});

Related

RowDataPacket returns empty object but it is not empty [React/Next]

I've been stressing around trying to fix this and I've burnt myself out. I'm calling my serverless mysql trying to get kanbans from teams. I've used this method multiple times and all were working fine but that is most likely because of they only return single item whilst this returns multiple items.
This is my code which returns empty object.
async function getKanbans(team_id){
let kanbans = [];
await sql_query(`SELECT id, sName FROM table WHERE iTeam = ?`, [team_id])
.then(result => {
result.forEach(kanban => {
// console.log(kanban);
kanbans.push({
id: kanban.id,
name: kanban.sName
});
});
})
.catch(err => {
console.log(err);
});
console.log(kanbans);
return kanbans;
}
As you can see.. I am trying to print kanbans and I do get:
[
{ id: 1, name: 'Kanban_1' },
{ id: 2, name: 'Kanban_2' }
]
of out it. Then I'm trying to return it to the item that called this function and this is how that looks like:
teams.push({
id : team.id,
sName : team.sName,
sColor : team.sColor,
aKanbans : result[0]['selectedTeam'] == team.id ? getKanbans(team.id) : null,
});
(a small snippet of something bigger)
Okay, so now when I try and look at the data response (from the frontend) I get this:
{
"success": true,
"message": "Found teams",
"teams": [
{
"id": 1,
"sName": "Team1",
"sColor": "#fcba03",
"aKanbans": {}
},
{
"id": 2,
"sName": "Team2",
"sColor": "#2200ff",
"aKanbans": null
}
]
}
aKanbans from Team1 is empty, empty object. What the **** do I do? I tried mapping it and still got an empty object. React/javascript is not my main language, I just like to learn. Any suggestions?
You are mixing async / await function with normal Promises handling.
Try to change your getKanbans code like this:
async function getKanbans(team_id) {
let kanbans = [];
try {
const result = await sql_query(
`SELECT id, sName FROM table WHERE iTeam = ?`,
[team_id]
);
result.forEach((kanban) => {
kanbans.push({
id: kanban.id,
name: kanban.sName,
});
});
} catch (err) {
console.log(err);
}
return kanbans;
}
And then populate the teams using (declare the parent async):
teams.push({
id : team.id,
sName : team.sName,
sColor : team.sColor,
aKanbans : result[0]['selectedTeam'] == team.id ? getKanbans(team.id) : null,
});

Include ressource link in Sequelize result

I'm building a rest api that uses Sequelize to interact with the database. A query looks like this:
function read_category(req, res) {
Category.findById(req.params.categoryId, {rejectOnEmpty: true}).then(category => {
res.json(category);
}).catch(Sequelize.EmptyResultError, function () {
res.status(404).json({message: 'No category found'});
}
).catch(function (err) {
res.send(err);
}
);
}
Now I want the category object that is returned from Sequelize and then returned to the user to include the linkto the ressource. I could do:
category.dataValues.link = config.base_url + 'categories/' + category.dataValues.id;
Which would result in:
{
"id": 1,
"name": "TestCategory 1",
"position": 1,
"createdAt": "2018-08-19T11:42:09.000Z",
"updatedAt": "2018-08-19T11:42:09.000Z",
"link": "http://localhost:3000/categories/1"
}
Since I have more routes than this one I'm wondering if there's a dynamic way to add the link property to every category. I don't want to save it in the database because the base-url might differ.
Thanks!
Better way to do it is , create a getter method :
const Category = sequelize.define( 'category' , {
....
your_fields
....
},
{
getterMethods:{
link() {
return config.base_url + 'categories/' + this.id;
}
}
});
module.exports = Category;
Then
Category.findAll(...).then(categories => {
// Now there is no need to append data manually , it will added each time when you query
console.log(categories); // <-- Check the output
})

Redux and Calendar repeating events

What should be the proper way of storing / handling repeating events in the redux store ?
Problem: Let's say that we have a backend API that generates repeating events trough a complicated business logic.Some of the events might have the same ID. Lets say that generated output looks this way :
[
{
"id": 1,
"title": "Weekly meeting",
"all_day": true,
"starts_at": "2017-09-12",
"ends_at": "2017-09-12"
},
{
"id": 3,
"title": "Daily meeting1",
"all_day": false,
"starts_at": "2017-09-12",
"ends_at": "2017-09-12",
},
{
"id": 3,
"title": "Daily meeting1",
"all_day": false,
"starts_at": "2017-09-13",
"ends_at": "2017-09-13",
},
{
"id": 3,
"title": "Daily meeting1",
"all_day": false,
"starts_at": "2017-09-14",
"ends_at": "2017-09-14",
}
]
Possible solution would be: generate unique ID by having additional property uid composed like this: id + # + starts_at. This way we could identify each occurrence uniquely. (I'm using this right now)
Example:
[
{
"id": 1,
"uid": "1#2017-09-12",
"title": "Weekly meeting",
"all_day": true,
"starts_at": "2017-09-12",
"ends_at": "2017-09-12"
}
]
I'm wondering is there some other way, maybe more elegant than having composed unique id ?
There is a possible pitfall with your current solution. What will happen if id and start_id of two events will be the same? Is it possible scenario in your domain?
Because of that I usually use this nice lib in such cases. It produces really short unique ids, which have some nice properties, like guaranties not to intersect, to be unpredictable and so on.
Also ask yourself if you actually need unique ids in your case. Looks like your back-end have no chance to distinguish events anyways, so why bother? Redux store will happily keep your events event without uid.
Maybe not much of an improvement (if at all) but just using JSON.stringify to check for duplicates could make unique id's obsolete.
const existingEvents = [
{
"id": 3,
"title": "Daily meeting1",
"all_day": false,
"starts_at": "2017-09-14",
"ends_at": "2017-09-14",
}
];
const duplicate = {
"id": 3,
"title": "Daily meeting1",
"all_day": false,
"starts_at": "2017-09-14",
"ends_at": "2017-09-14",
};
const eventIsDuplicate = (existingEvents, newEvent) => {
const duplicate =
existingEvents.find(event => JSON.stringify(event) == JSON.stringify(newEvent));
return typeof duplicate != 'undefined';
};
console.log(eventIsDuplicate(existingEvents, duplicate)); // true
I guess this would only be preferable to your existing solution if, for some reason, you'd want to keep all the uniqueness logic on the client side.
As far as I understand the examples you've given, it seems like the server is sending a particular event whenever the details of the event change.
If that is so, and you want to track the changes to events, your might shape might be an array of objects with all the fields of the event that hold the current data, and a history property which is an array of all previous (or n most recent) event objects and the timestamps at which they were received. This is how your reducers would look, storing only the five most recent event changes for each event. I'm expecting the action to have a payload property which has your standard event property and a timestamp property, which can be easily accomplished in the action creator.
const event = (state = { history: [] }, action) => {
switch (action.type) {
case 'EVENT_FETCHED':
return ({
...action.payload.event,
history: [...state.history, action.payload].slice(-5),
});
default:
return state;
}
};
const events = (state = { byID: {}, IDs: [] }, action) => {
const id = action.payload.event.ID;
switch (action.type) {
case 'EVENT_FETCHED':
return id in state.byID
? {
...state,
byID: { ...state.byID, [id]: event(state.byID[id], action) },
}
: {
byID: { ...state.byID, [id]: event(undefined, action) },
IDs: [id],
};
default:
return state;
}
};
Doing this, you don't need any unique ID. Please let me know if I have misunderstood your problem.
Edit: This is a slight extension of the pattern in the Redux documentation, to store previous events.
At the end this is what I've implemented (for demonstration purpose only - unrelated code is omitted):
eventRoot.js:
import { combineReducers } from 'redux'
import ranges from './events'
import ids from './ids'
import params from './params'
import total from './total'
export default resource =>
combineReducers({
ids: ids(resource),
ranges: ranges(resource),
params: params(resource)
})
events.js:
import { GET_EVENTS_SUCCESS } from '#/state/types/data'
export default resource => (previousState = {}, { type, payload, requestPayload, meta }) => {
if (!meta || meta.resource !== resource) {
return previousState
}
switch (type) {
case GET_EVENTS_SUCCESS:
const newState = Object.assign({}, previousState)
payload.data[resource].forEach(record => {
// ISO 8601 time interval string -
// http://en.wikipedia.org/wiki/ISO_8601#Time_intervals
const range = record.start + '/' + record.end
if (newState[record.id]) {
if (!newState[record.id].includes(range)) {
// Don't mutate previous state, object assign is only a shallow copy
// Create new array with added id
newState[record.id] = [...newState[record.id], range]
}
} else {
newState[record.id] = [range]
}
})
return newState
default:
return previousState
}
}
There is also a data reducer but it's linked in parent reducer due to generic implementation that is re-used for common list responses. Events data are updated and start/end property is removed as it's composed by range (ISO 8601 time interval string). This can be later used by moment.range or split by '/' to get start/end data. I've opted for array of range strings to simplify checking of existing ranges, as they might grow large. I think that primitive string comparison (indexOf or es6 includes) would be faster than looping over complex structure in such cases.
data.js (stripped down version):
import { END } from '#/state/types/fetch'
import { GET_EVENTS } from '#/state/types/data'
const cacheDuration = 10 * 60 * 1000 // ten minutes
const addRecords = (newRecords = [], oldRecords, isEvent) => {
// prepare new records and timestamp them
const newRecordsById = newRecords.reduce((prev, record) => {
if (isEvent) {
const { start, end, ...rest } = record
prev[record.id] = rest
} else {
prev[record.id] = record
}
return prev
}, {})
const now = new Date()
const newRecordsFetchedAt = newRecords.reduce((prev, record) => {
prev[record.id] = now
return prev
}, {})
// remove outdated old records
const latestValidDate = new Date()
latestValidDate.setTime(latestValidDate.getTime() - cacheDuration)
const oldValidRecordIds = oldRecords.fetchedAt
? Object.keys(oldRecords.fetchedAt).filter(id => oldRecords.fetchedAt[id] > latestValidDate)
: []
const oldValidRecords = oldValidRecordIds.reduce((prev, id) => {
prev[id] = oldRecords[id]
return prev
}, {})
const oldValidRecordsFetchedAt = oldValidRecordIds.reduce((prev, id) => {
prev[id] = oldRecords.fetchedAt[id]
return prev
}, {})
// combine old records and new records
const records = {
...oldValidRecords,
...newRecordsById
}
Object.defineProperty(records, 'fetchedAt', {
value: {
...oldValidRecordsFetchedAt,
...newRecordsFetchedAt
}
}) // non enumerable by default
return records
}
const initialState = {}
Object.defineProperty(initialState, 'fetchedAt', { value: {} }) // non enumerable by default
export default resource => (previousState = initialState, { payload, meta }) => {
if (!meta || meta.resource !== resource) {
return previousState
}
if (!meta.fetchResponse || meta.fetchStatus !== END) {
return previousState
}
switch (meta.fetchResponse) {
case GET_EVENTS:
return addRecords(payload.data[resource], previousState, true)
default:
return previousState
}
}
This can be then used by an calendar component with event selector:
const convertDateTimeToDate = (datetime, timeZoneName) => {
const m = moment.tz(datetime, timeZoneName)
return new Date(m.year(), m.month(), m.date(), m.hour(), m.minute(), 0)
}
const compileEvents = (state, filter) => {
const eventsRanges = state.events.list.ranges
const events = []
state.events.list.ids.forEach(id => {
if (eventsRanges[id]) {
eventsRanges[id].forEach(range => {
const [start, end] = range.split('/').map(d => convertDateTimeToDate(d))
// You can add an conditional push, filtered by start/end limits
events.push(
Object.assign({}, state.events.data[id], {
start: start,
end: end
})
)
})
}
})
return events
}
And here is how the data structure looks in the redux dev tools:
Each time the events are fetched, their data is updated (if there is a change) and references are added. Here is an screenshot of redux diff after fetching new events range:
Hope this helps somebody, I'll just add that this still isn't battle tested but more a proof of a concept that's working.
[EDIT] Btw. I'll probably move some of this logic to the backend as then there will be no need to split / join / delete properties.

Unable to Insert Data into a collection

I have officer Schema in which if a user wants to fix an appointment, his entry is made in the DB. The schema is:
officerSchema = mongoose.Schema({
email : {type: String,
index: { unique: true }
},
appointmentList : Array // array of jsonObject of dates and userID
});
The AppointmentList is an array of JSON Objects which contains the ID of the officer with which appointment has to be made, date and userID (the user which wants to fix the appointment).
However to avoid duplicate appointment entries, I have been using several methods mentioned on the internet. None of them have worked for me so far. I am posting the code below. The problem with below code is it NEVER inserts any data in the appointmentsList. However if I use save() instead of update() insertion occurs but duplicates also get inserted.
Here is the JSON Object that I want to add in the array from DB,
{
"id": "1321231231",
"appointment": {
"userID": "31321",
"date": "24 March"
}
}
var ID = requestObject.id;
var newObject = {$addToSet: requestObject.appointment};
OfficerModel.findOne({_id : ID}, function(err, foundData) {
if(err) {
console.log(err);
return;
}
else {
var dbList = foundData.list;
dbList.push(newObject);
foundData.update(function(err, updatedData) {
if(err) {
console.log( err);
}
else {
console.log("successful");
}
});
}
});
Using the $addToSet operator might work for you.
var appt = {
id: "1321231231",
appointment: {
userID: "31321",
date: "24 March"
}
}
Officer.update(
{_id: ID},
{$addToSet: {appointmentList: appt}},
function(err) { ... }
);
But it's not a perfect solution because {one: 1, two: 2} and {two: 2, one: 1} aren't interpreted as equal, so they could both get added to an array with $addToSet.
To totally avoid duplicates, you could do something like this:
var appt = {
id: "1321231231",
appointment: {
userID: "31321",
date: "24 March"
}
};
Officer.findOne(
{_id: ID, 'appointmentList.id': appt.id},
function(err, officerDoc) {
if (err) { ... }
// since no document matched your query, add the appointment
if (!officerDoc) {
Officer.update(
{_id: ID},
{$push: {appointmentList: appt}},
function(err) { ... }
);
}
// since that appointment already exists, update it
else {
Officer.update(
{_id: ID, 'appointmentList.id': appt.id},
{$set: {'appointmentList.$.appointment': appt.appointment}},
function(err) { ... }
);
}
}
);
The operation above that updates the existing appointment uses the positional operator.

underscorejs: how to get unique categories from merged properties on an array of objects

difficult title for a simple issue :)
say we have a list of books
they are in different categories, these categories are an array property on the book
we want to transform this json, into a list of unique categories, with the books under the category
first off: the json:
[
{
"description":"book 1 description",
"title":"book 1 title",
"id":"4",
"logo":"",
"image":"",
"categories":[
{
"id":"1",
"title":"Logistiek"
},{
"id":"2",
"title":"Finances"
}
]
},
{
"description":"book 2 description",
"title":"book 2 title",
"id":"1",
"logo":"",
"image":"",
"categories":[
{
"id":"3",
"title":"Telecom"
}
]
},
{
"description":"book 3 description",
"title":"book 3 title",
"id":"2",
"logo":"",
"image":"",
"categories":[
{
"id":"3",
"title":"Telecom"
}
]
},
{
"description":"book 4 description",
"title":"book 4 title",
"id":"3",
"logo":"",
"image":"",
"categories":[
{
"id":"2",
"title":"Finances"
}
]
}
]
now what i managed myself:
i started by mapping off all the categories:
var data = {} // lets say all json is inhere...
var res = _(data).map(function(m){
return m.categories;
});
this I flatten into 1 array of categories (because now its an array per book.
res = _(res).flatten();
this gives me an array of all category items, though this has doubles in it.
now i'm not getting much further than this yet.
i tried using the union method before flattening but that didnt help out
i tried the uniq on the bigger array but i think i have to break em down into separate arrays for the uniq to work
i'm kind of stuck getting the unique values out of that array of categories.
after that I can manage to add the books under the categories
If anyone got some ideas, or maybe tell me that i'm doing this completely wrong :) go ahead tell me, if i can do it shorter or with better performance by going another direction feel free to throw it at me.
update1
ok, now i got a little further but i'm pretty sure it's not ideal, (too many steps, i get a feeling getting a unique list could go quicker than these steps)
// get all categorie arrays
var res = _(data).map(function(m){
return m.categories;
});
// flatten them
res = _(res).flatten();
// reduce to unique ID array
var catIds = _(res).pluck('id');
catIds = _(catIds).uniq();
// from here on create an array with unique categories
var cats = [];
_(catIds).each(function(cId){
var s = _(res).filter(function(c){
return c.id === cId;
});
cats.push(_(s).first());
});
can I do this quicker?
see jsFiddle in action...
http://jsfiddle.net/saelfaer/JVxGm/
update 2
ok, i got further, thanks to the help from you guys below,
but i still feel like using 2 eaches is not the best way to get to the end.
var json = [] // lets say the above json is in this variable.
var books = _(json).map(function(book) {
var cats = book.categories;
delete book.categories;
return _(cats).map(function(cat) {
return _({}).extend(book, { category: cat });
});
});
books = _(books).flatten();
var booksPerCategory = [];
_(books).each(function(book){
if(!_(booksPerCategory).any(function(cat){
return cat.id === book.category.id;
}))
{ booksPerCategory.push(book.category); }
});
_(booksPerCategory).each(function(cat){
var mods = _(books).filter(function(book){
return book.category.id === cat.id;
});
cat['modules'] = mods;
});
you can see what i wanted to recieve: the booksByCategory array
and what i got via the help from below: the books object
both in this jsfiddle: http://jsfiddle.net/saelfaer/yWCgt/
Here's some help, it does not solve your entire problem, but I think you should be able to take it from there ;)
The key is in using groupBy. Something like the following should give you a good start!
_.groupBy(data, function (book) {
return _.map(book.categories, function (category) {
return category.id;
});
});
You could do something like this:
var books = ​_(json).map(function(book) {
var cats = book.categories;
delete book.categories;
return _(cats).map(function(cat) {
return _({}).extend(book, { category: cat.id });
});
});
books = _(books).flatten();
books = _(books).groupBy(function(book) { return book.category });
Demo: http://jsfiddle.net/ambiguous/jaL8n/
If you only want a slice of each book's attributes then you can adjust this:
return _({}).extend(book, { category: cat.id });
accordingly; for example, if you just want the titles and category IDs:
return _(cats).map(function(cat) {
return {
category: cat.id,
title: book.title
};
});
Demo: http://jsfiddle.net/ambiguous/EFLDR/
You don't need to explicitly generate a set of unique category IDs, you just have to arrange your data appropriately and everything falls out on its own (as is common with functional approaches).
UPDATE: Based on the comments, I think you just need to add a reduce after the flatten instead of a groupBy:
var books = _(json).map(function(book) {
var cats = book.categories;
delete book.categories;
return _(cats).map(function(category) {
return {
category: category,
book: book
};
});
});
books = _(books).flatten();
books = _(books).reduce(function(h, b) {
if(!h[b.category.id])
h[b.category.id] = _(b.category).extend({ modules: [ ] });
h[b.category.id].modules.push(b.book);
return h;
}, { });
Demo: http://jsfiddle.net/ambiguous/vJqTz/
You could also use chain and some named functions to make it easier to read:
function rearrange(book) {
var cats = book.categories;
delete book.categories;
return _(cats).map(function(category) {
return {
category: category,
book: book
};
});
}
function collect_into(h, b) {
if(!h[b.category.id])
h[b.category.id] = _(b.category).extend({ modules: [ ] });
h[b.category.id].modules.push(b.book);
return h;
}
var books = _.chain(json).
map(rearrange).
flatten().
reduce(collect_into, { }).
value();
Demo: http://jsfiddle.net/ambiguous/kSU7v/