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

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/

Related

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
})

Sequelize update a record and it's associations

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);
});

Dividing a sorted list

I have a list of movies and need to group them in both c# (or angular is also acceptable) and css very similary to the image provided here underneath. Any ideas on how to wire the html and c# and how to use the .groupBy() or something similar please ?
This is what I've got so far:
HTML (a list of all my movies in alphabetical order):
<div class="movs">
<movies-collection movies="::vm.sortedMovies" order-by="name"></movies-collection>
</div>
Typescript:
static id = "MoviesController";
static $inject = _.union(MainBaseController.$baseInject, [
"sortedMovies"
]);
static init = _.merge({
sortedMovies: ["allMovies", (movies: Array<Models.IGov>) => {
return _.sortBy(movies, "content.name");
}]
All my movies are already sorted alphabteically I just need to with the help of css structure them similarly to this image
I would create a filter that adds a "$first" property to the movie. If it is the first in a sorted list that starts with the character, then $first would be true. Bind to $first in your view when you show the character in uppercase.
The following demonstrates this idea:
var app = angular.module('app',[]);
app.controller('ctrl', function($scope) {
$scope.movies = [
{ title: 'The Godfather' },
{ title: 'Fargo' },
{ title: 'Sniper' },
{ title: 'Terminator'},
{ title: 'Click'},
{ title: 'Cake' },
{ title: 'Frozen' },
{ title: 'Casino Jack' },
{ title: 'Superman' },
{ title: 'The Matrix' }
];
});
app.filter('applyFirst', function() {
return function (movies) {
for(var i = 0; i < movies.length; ++i) {
if (i == 0)
movies[i].$first = true;
else {
if (movies[i].title.toLowerCase()[0] != movies[i-1].title.toLowerCase()[0]) {
movies[i].$first = true;
}
else {
movies[i].$first = false;
}
}
}
return movies;
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0-beta.1/angular.js"></script>
<div ng-app = "app" ng-controller="ctrl">
<div ng-repeat="movie in movies | orderBy:'title' | applyFirst">
<h1 ng-if="movie.$first">{{ movie.title[0] | uppercase }}</h1>
{{ movie.title }}
</div>
</div>
It's not possible in css, your code must split the array of movies into an array of letters, each with an array of movies.
You can use reduce for that:
var groupedMovies = movies.reduce((lettersArray, movie, idx, arr) => {
var firstLetter = movie[0].toUpperCase();
if (!lettersArray[firstLetter]) {
lettersArray[firstLetter] = [movie];
}
else {
lettersArray[firstLetter].push(movie);
}
return lettersArray;
}, []);
The result will look something like this:
[ T: [ 'The Avengers', 'Tower Quest', 'ThunderFist', 'Transformers' ],
U: [ 'Untamed Bengal Tiger', 'Untamed Giant Panda' ],
V: [ 'Victorious' ] ]
This way you can do a loop on the letters array, and in each do another loop for each movie.
The best practice for that would be to create a directive for a grouped movies, it will receive the letter and the inner array of movies in that letter.

AngularJS Filter REST JSON in Controller

I need to output a count of items from JSON by category (using .length I believe), and would like to manage this in a controller so I can place it to scope anywhere I want. How can I filter REST JSON in a controller?
My sample JSON is as follows:
[
{
"id": "66D5069C-DC67-46FC-8A51-1F15A94216D4",
"articletitle": "artilce1",
"articlecategoryid": 1,
"articlesummary": "article 1 summary. "
},
{
"id": "66D5069C-DC67-46FC-8A51-1F15A94216D5",
"articletitle": "artilce2",
"articlecategoryid": 2,
"articlesummary": "article 2 summary. "
},
{
"id": "66D5069C-DC67-46FC-8A51-1F15A94216D6",
"articletitle": "artilce3",
"articlecategoryid": 3,
"articlesummary": "article 3 summary. "
},
{
"id": "66D5069C-DC67-46FC-8A51-1F15A94216D7",
"articletitle": "artilce4",
"articlecategoryid": 1,
"articlesummary": "article 3 summary. "
},
]
My Resource is as follows:
// Articles by ID
pfcServices.factory('pfcArticles', ['$resource', function ($resource) {
return $resource('https://myrestcall.net/tables/articles', {},
{
'update': { method:'PATCH'}
});
}]);
My Controller is as follows:
// Count number of total articles
pfcControllers.controller('pfcArticleCountCtrl', ['$scope', 'pfcArticles', function ($scope, pfcArticles) {
$scope.articlecount = pfcArticles.query();
I believe I would need to add something to the controller (ex. $scope.cat1 = filter(my logic to get my category here), but am a little lost on how to query this so I have a count of items per category.
Build a filter like this.
angular.module('FilterModule', [])
.filter( 'cat1', function() {
return function(yourJSON) {
var count = 0;
yourJSON.forEach(function(item) {
switch(item.articlecategoryid) {
case 1:
count++;
break;
default:
console.log('item is not of category 1');
}
});
return count;
};
});
Inject the $filter service and fetch the cat1 filter. Now you can use it as $scope.cat1 = $filter('cat1')(yourJSON);

use ko.utils.arrayfilter to return collection based on value of inner collection

I am using knockout and want to use arrayFilter to return array of objects where a property "modified" inside of another array has value of true.
i.e.
my json object looks like
Family tree
Family{
LastName=Smith
Children{
Name=bob,
Modified=false},
{
Name=tom, Modified=true}
}
Family{
LastName=Jones
Children{
Name=bruno,
Modified=false},
{
Name=mary, Modified=false}
}
The result of the array filter would be (as follows) becuase child tom has modified =true
FamilyTree
Family{
LastName=Smith
Children{
Name=bob,
Modified=false},
{
Name=tom, Modified=true}
}
is this possible?
I think the solution that #pax162 supplied probably answers your question. However, there is the question of usage. The proposed solution is going to perform nested iterations. If you are only expecting to be processing the data once (as opposed to driving some rich client views), this is the approach to take. On the other hand, if you are binding this data to a view, you might consider another more KO-like approach. Here's what I have in mind:
var family = function(data){
var self = {
LastName :ko.observable(data.LastName),
Children : ko.observableArray( data.Children || [])
};
family.hasModifiedChildren = ko.computed(function() {
return ko.utils.arrayFirst(this.Children(),
function(child) {
return child.Modified === true;
}) !== null;
}, family);
return self;
}
Rather than using JSON data, create observable JS objects as such:
var families = return ko.utils.arrayMap(familiesJson, family);
// or, if you need an observable:
families = ko.observableArray(ko.utils.arrayMap(familiesJson, family));
Finally, get your list of families with modified children like this:
var familiesWithModifiedChildren = ko.computed(function() {
return ko.utils.arrayFilter(families, function(fam) {
return fam.hasModifiedChildren();
});
});
If you are building a live-update page, you'll want to go with this style of view model. This will allow Knockout to utilize its observer optimizations rather than building a new array every time the function is evaluated. Hope this helps!
If you want to get only families with at least one modified child, you can do this (http://jsfiddle.net/MAyNn/3/) . The json was not valid, changed it a bit.
var families = [
{
LastName :"Smith",
Children : [{
Name:"bob",
Modified:false},
{
Name:"tom", Modified :true}
]
},
{
LastName :"Jones",
Children : [{
Name:"bruno",
Modified:false},
{
Name:"mary", Modified :false}
]
}
];
var filteredFamilies = ko.utils.arrayFilter(families, function(family){
var hasModified = false;
var first = ko.utils.arrayFirst(family.Children, function(child){
return child.Modified;
})
if (first) {
hasModified = true;
}
return hasModified;
})
console.log(families);
console.log(filteredFamilies);