Updating an object that is used inside another object - javascript

I have an app that allows you to create employees, but I have a problem when renaming employee positions.
I have an array of positions that looks something like this:
positions: [
{ id: 1, title: 'Masseuse' },
...
];
If I create an employee, I have to select a position from a dropdown, and the employees end up looking something like this:
employees: [
{ id: 1, name: 'John Doe', title: 'Masseuse' },
...
];
This approach works fine until the user renames a position. For example, if the user renames 'Masseuse' to 'Massage Therapist', the position dropdown will update as expected, but the employees' with that position will still say 'Masseuse'.
If a user renames a position, do I also need to find each employee with that position and update them individually? Or is there a different approach that I should be taking? I'm wondering if the employee object should store the position ID since that will never change and then somehow use that to display their position title, but I don't know how that would work.
I'm not experienced with backend development or database architecture yet, so this may not be important information, but I'm only using a fake REST API at the moment. I will eventually setup an actual database, but haven't gotten to that yet.
FWIW, I'm using Angular and the following mock API: https://github.com/typicode/json-server

You could use the position id to store the title. And then display the title by the stored position id.
employees: [
{ id: 1, name: 'John Doe', titleId: 1 },
...
];
Then your dropdown would be like below
<select [(ngModel)]="editingEmployee.titleId"">
<option *ngFor="let x of positions" [value]="x.id">{{x.title}}</option>
</select>
And now when you are displaying an employee, you could refer to the positions array from the stored titleId of the employee.
<div *ngFor="let employee of employees">
{{employee.name}} is a {{getPosition(employee.titleId)}}
</div>
And in your component
getPosition(titleId) {
const position = this.positions.filter(p => p.id === titleId);
return position[0] ? position[0].title : '';
}
Refer to this working stackblitz

Nice reasoning.
What you are thinking is the correct way to go about it.
This aligns with the concept of database normalization which is a structured way of reducing redundancy in your architecture.
Se also database normalization in wikipedia: link

One possible solution will be to store a position id instead of title inside employee object.
And when you need to display it you can construct "view" of that object:
let positions = [
{ id: 1, title: 'Masseuse' },
...
];
let employees = [
{ id: 1, name: 'John Doe', positionId: 1 },
...
];
function getPositionTittle(id, positions) {
const position = positions.find(p => p.id === id);
if (!position) return '';
return position.title;
}
function prepare(employee, positions) {
const title = getPositionTittle(employee.positionId, positions);
return {
...employee,
title
};
}
const employeeView = prepare(employees[0], positions);
// employeeView = { id: 1, name: 'John Doe', positionId: 1, title: 'Masseuse' }

Related

Excluding results from Objection/Knex query based on withGraphFetched results

I have two models in Objection - "brands" and "offers".
Brand:
const { Model } = require('objection')
class Brand extends Model {
static get tableName() {
return 'brands'
}
...
static get relationMappings() {
const Offer = require('./offer-model')
return {
offer: {
relation: Model.HasManyRelation,
modelClass: Offer,
join: { from: 'brands.id', to: 'offers.brand_id' }
}
}
}
}
Offer:
const { Model } = require('objection')
class Offer extends Model {
static get tableName() {
return 'offers'
}
}
A brand has many offers, but I want to get brands which have at least 1 offer using withGraphFetched, excluding brands which have no offers. Here's what I have so far:
const brandModel = this.objection.models.brand
const query = brandModel.query().withGraphFetched('offer')
query.page(page, page_size)
const offers = await query
This returns the "joined" data, but also returns brands which don't have offers. For example:
[{
id:1,
name: 'brand 1',
offers: [{offerId: 1, offerName: 'offer 1'}]
},{
id:2,
name: 'brand 2',
offers: []
}]
In the above data, I don't want the brand with ID 2 to be in the result set.
I am using Objection/Knex to paginate the results, so I can't just exclude the brands with empty object arrays after the query has been executed.
I can achieve this using raw queries, but that means I can't use the Objection dynamic attributes and a few other key parts of Objection.
Thanks!
You can just tack a whereExists onto the query; something like
const query = brandModel.query()
.withGraphFetched('offer')
.whereExists(
(qb) => qb.select('id').from('offers')
.where('offers.brand_id', knex.ref('brands.id'))
);
Even though the whereExists bit is directly Knex, the query still goes through your models so stuff you've defined there should still apply (maybe unless you're doing something very wild that directly affects the columns used inside the whereExists)

Add a filter to GTM custom javascript variable array

We have a ecommerce system that pushes through a datalayer. Unfortunately, the datalayer includes the 'building blocks' of our products. Resulting in tags that register more information than I would like. I would like to exclude these 'building blocks' through a custom javascript variable. See an example of the datalayer below.
checkout: {
actionField: {step: 1},
currencyCode: 'EUR',
products: [
{
name: 'product name',
id: '40291',
price: '149,00',
category: 'Transportation/Other',
quantity: 2
},
{
name: 'building block 1',
id: '20231',
price: '0,00',
category: 'Transportation/Other',
quantity: 2
},
{
name: 'building block 2',
id: '12302',
price: '0,00',
category: 'Transportation/Other',
quantity: 2
I've made a CJS variables that makes the products.id, .name, .price and .quantity an array (see below), but I am unable to figure out how to add a filter() that excludes from the array the values where corresponding product.name = "building block 1" && "building block 2".
var products = {{dlv - product}};
return products.reduce(function(arr, prod) { return arr.concat(prod.id); }, []);
Would anybody be able to help me out with this part of the function? Alternatively, I was thinking I might be able to achieve the same with a conditional if() statement.
Thanks in advance!
I'm not sure how GTM works, but if you want to filter an array with JS you could use something like this:
checkout.products.filter(product => !product.name.includes('building block'))
Let me know if this helps or I'm missing something.
I had the same challenge. My solution:
1. Create data layer variable with yours products array
In my case it was ecommerce.impressions data layer variable from productImpression event.
2. Choose parameters for your filter
I had:
variable with products array from step 1 - {{products array}}
term - only products with this word will be in my filtered array
custom dimension - 'dimensionXX' - this parameter will be check
3. Create custom javascript variable with filtered products array
Paste this function with yours filter parameters
function() {
var items = {{products array}};
var filtered_items = items.filter(function(filter) {
return filter.dimensionXX.includes('term');
}
);
return filtered_items;
}
4. Add your custom javascript variable from step 3 to your tag configuration
Replace your original product array with this custom javascript variable to send filtered product array to GA.

Normalizr: How to normalize nested keys and get back to same shape?

This is probably much clearer with code. If I have a response like this.
{
id: "1",
container: {
sections: [
{
id: "a",
pages: [
{
id: "z"
}
]
}
]
}
}
I am really wanting to normalize the sections collection and the pages collection inside of them. I also need to get it back into this exact shape. The problem that is that container does not have an id. Everything I try can't seem to get it back to this. I would imagine the normalized version of what I am going for would be something like.
{
// the thing that container is in
project: {
id: "1",
// maybe remove container key, and know I have to normalize
// and add the key again?
container: "?"
},
sections: {
id: "a": {
pages: ["z"]
},
pages: {
id: "z"
}
}
Any help would be appreciated. Again the sections and pages are really what I am trying to normalize and de-normalize without loosing data from the top level key
EDIT
To slightly rephrase the question I have found posts like this one Normalizr - is it a way to generate IDs for non-ids entity model?. That are trying to add ids to things that don't have them. I am not really wanting that because I do not want to normalize anything at the same level or above container only below.
You may include container as part of your project schema definition.
const { schema } = require('normalizr');
const pageSchema = new schema.Entity('pages');
const sectionSchema = new schema.Entity('sections', {
pages: [pageSchema],
});
const projectSchema = new schema.Entity('projects', {
container: {
sections: [sectionSchema],
},
});
In this case, container will not be normalized. It's difficult to treat them as entities as long as they don't posess any identifying properties. If they did, you could specify that property as idAttribute.
Your best option is to transform API response an generate unique ids at some level between API and normalizr, but it won't give you any of the normal benefits you get with normalization as pointed out in the question you linked.
Here's how you denormalize an entity from normalized state using the above schema:
const { normalize, denormalize } = require('normalizr');
const data = {
id: "1",
container: {
sections: [
{
id: "a",
pages: [
{
id: "z"
}
]
}
]
}
};
const state = normalize(data, projectSchema);
// -> {"entities":{"pages":{"z":{"id":"z"}},"sections":{"a":{"id":"a","pages":["z"]}},"projects":{"1":{"id":"1","container":{"sections":["a"]}}}},"result":"1"}
denormalize(1, projectSchema, state.entities);
// -> {"id":"1","container":{"sections":[{"id":"a","pages":[{"id":"z"}]}]}}

Properly returning nested state with Object.assign

I'm working on a React/Redux application and for the most part, everything has been working smoothly.
Essentially, it's a todo application that has categorization built in.
I'm having trouble properly returning the full existing state in my reducer when the user adds a todo-item inside a category.
The redux state before I dispatch the ADD_ITEM action looks like this:
{
items: {
"HOME": [["Do laundry", "High Priority"],["Feed kids", "Low priority"] ],
"WORK": [["Get promotion", "High priority"],["Finish project", "Medium priority"] ],
"BOTH": [["Eat better", "Medium priority"],["Go for a run", "High priority"] ],
},
settings: {
Test: "test"
}
}
The user navigates to a category(pre-made, haven't implemented creating them yet) and can create a new todo-item with a name and a priority. This dispatches an action that returns an array like [category, name, priority].
Currently in my reducer, I have it where it is successfully adding the item, but it is emptying/overwriting all the existing categories.
My reducer looks like this:
case types.ADD_ITEM:
let cat = action.payload[0];
let name = action.payload[1];
let priority = action.payload[2];
return Object.assign({}, state, { items: { [cat]: [...state.items[cat], [name, priority]]}});
I've tried creating a new object first with all the combined items like so:
let combinedItems = Object.assign({}, state.items, { [cat]: [...state.items[cat], action.payload] });
If I console.log the above combinedItems, I get the exact object that I want items to be. However, I'm struggling to have the final object returned by the reducer to reflect that.
When I tried something like below, I got an object that contained combinedItems as a separate key inside items.
return Object.assign({}, state, { items: { combinedItems, [cat]: [...state.items[cat], [name, priority]]}});
Can anyone help me get my final redux state to contain all the existing categories/items + the user added one? I would really appreciate the help.
I think you should use objects in places where you have arrays. In your action payload, instead of:
[category, name, priority]
You can have:
{category, name, priority}
action.payload.category
I would make the same change with your todo items. Instead of:
[["Eat better", "Medium priority"], ... ]
You can have:
[{ name: "Eat better", priority: "Medium" }, ... ]
Now in terms of whether it's better to make items an object with category keys or an array of items that know its category... well I think the latter is better, that way if you get a single item, you don't need to go up to its parent to find out which category it belongs to. It would also make your problem a bit more manageable.
items: [
{
name: "Eat better",
priority: "Medium",
category: "Both"
}, ...
]
Putting this all together to solve your problem:
case types.ADD_ITEM:
let newItem = {
name: action.payload.name,
priority: action.payload.priority,
category: action.payload.category
}
return Object.assign({}, state, { items: [ ...state.items, newItem ] })
Whatever benefit you had before with categories as keys are trivial to reproduce with this structure.
Get all items in the HOME category:
this.props.items.filter(item => item.category === 'HOME')

How to control array sorting in knockout.mapping?

Please find the jsfiddle example. I have an observable array of persons, each person has id, firstName and lastName. User sorts this array by any property, in both directions.
Then, at some point, I need to update my array, which I do using knockout.mapping.fromJS function:
this.rockStarsMapping = {
create: function (options) {
// just to make sure that key works
console.log('created');
return options.data;
},
key: function(data) {
return data.id;
}
};
this.rockStars = ko.observableArray([]);
this.getRockStars = function() {
// here should be some ajax call instead of a stub
var newRockStars = [
{ id: 1, firstName: "John", lastName: "Lehnon" },
{ id: 2, firstName: "Paul", lastName: "McCartney" },
...
];
ko.mapping.fromJS(newRockStars, self.rockStarsMapping, self.rockStars);
};
The problem is, that new array is sorted by id (for example), and this sorting prevails over sorting in existing array.
My current solution is to remember current sort column name and sort direction, but it doesn't work right, because other columns may have other sort directions. For example, in my jsfiddle code, try to sort by Id descending first (6, 5, 4, ...), then sort by First name. Now if you click "Get rock stars", the sorting order in Id column changes, which is not a desired behavior.
How to keep sorting order as is? And what more important - how to make sure new items would be in the right place of an array?

Categories

Resources