Source object access from ColumnSet - node.js

I try to split a sub-object in my recordset when importing data with initCB properties of a Column in ColumnSet.
But when I use two different init functions for two different destination names but one source I get same result.
const cs = new pgp.helpers.ColumnSet([
'id',
{ name: 'source_id', prop: 'source', init: function(obj) { return obj.value.id; } },
{ name: 'source_name', prop: 'source', init: function(obj) { return obj.value.name; } },
], { table: 'test_table' });
const data = [
{ id: 1, source: { id: 1, name: 'source1' } },
{ id: 2, source: { id: 1, name: 'source1' } },
{ id: 3, source: { id: 2, name: 'source2' } },
];
const insert = pgp.helpers.insert(data, cs);
The result is:
INSERT INTO "test_table"("id","source_id","source_name") VALUES
(1,'source1','source1'),
(2,'source1','source1'),
(3,'source2','source2')
instead of expected:
INSERT INTO "test_table"("id","source_id","source_name") VALUES
(1,1,'source1'),
(2,1,'source1'),
(3,2,'source2')
It seems like second invocation of callback function for THE SAME source field overriding result of previous invocation of ANOTHER callback function on THIS source field.
How I can avoid this?
Or there is another way of splitting a sub-object during import?

Option prop doesn't quite work that way. It is there to remap the value to a different property name, but it does not supply the direct object reference.
Instead, use property source of the column descriptor, to reference the source object. Ironically, you called the property in your data source as well, which means you will have to use source twice in your reference:
const cs = new pgp.helpers.ColumnSet([
'id',
{name: 'source_id', init: c => c.source.source.id},
{name: 'source_name', init: c => c.source.source.name}
], {table: 'test_table'});
The first source is what pg-promise API supports, while the second is your data column name :)
Also, as per documentation, the API sets source and this to the same, so if you prefer the ES5 function syntax (looks cleaner for your example), then you can do this instead:
const cs = new pgp.helpers.ColumnSet([
'id',
{ name: 'source_id', init: function() {return this.source.id;}},
{ name: 'source_name', init: function() {return this.source.name;}},
], { table: 'test_table' });
Above we have this point at the source data object.

Related

Storing and querying JSON arrays in Redisjson with nodejs

What I was hoping to do was store an array of objects using RedisJSON very simply and then query that array.
I have something similar to this:
const data = [
{
_id: '63e7d1d85ad7e2f69df8ed6e',
artist: {
genre: 'rock',
},
},
{
_id: '63e7d1d85ad7e2f69df8ed6f',
artist: {
genre: 'metal',
},
},
{
_id: '63e7d1d85ad7e2f69df8ed6g',
artist: {
genre: 'rock',
},
},
]
then I can easily store and retrieve this:
await redisClient.json.set(cacheKey, '$', data)
await redisClient.json.get(cacheKey)
works great. but now I want to also query this data, I've tried creating an index as below:
await redisClient.ft.create(
`idx:gigs`,
{
'$.[0].artist.genre': {
type: SchemaFieldTypes.TEXT,
AS: 'genre',
},
},
{
ON: 'JSON',
PREFIX: 'GIGS',
}
)
and when I try and search this index what I expect is it to return the 2 documents with the correct search filter, but instead it always returns the entire array:
const searchResult = await redisClient.ft.search(`idx:gigs`, '#genre:(rock)')
produces:
{
total: 1,
documents: [
{ id: 'cacheKey', value: [Array] }
]
}
I can't quite work out at which level I'm getting this wrong, but any help would be greatly appreciated.
Is it possible to store an array of objects and then search the nested objects for nested values with RedisJSON?
The Search capability in Redis stack treats each key containing a JSON document as a separate search index entry. I think what you are doing is perhaps storing your whole array of documents in a single Redis key, which means any matches will return the document at that key which contains all of your data.
I would suggest that you store each object in your data array as its own key in Redis. Make sure that these will be indexed by using the GIGS prefix in the key name, for example GIGS:63e7d1d85ad7e2f69df8ed6e and GIGS:63e7d1d85ad7e2f69df8ed6f.
You'd want to change your index definition to account for each document being an object too so it would look something like this:
await redisClient.ft.create(
`idx:gigs`,
{
'$.artist.genre': {
type: SchemaFieldTypes.TEXT,
AS: 'genre',
},
},
{
ON: 'JSON',
PREFIX: 'GIGS:',
}
)
Note I also updated your PREFIX to be GIGS: not GIGS - this isn't strictly necessary, but does stop your index from accidentally looking at other keys in Redis whose name begins GIGS<whatever other characters>.

Node Discord.js Axios API Indexing

I want to index through a list of items stored in a json file and call to each API and bring back data. The below code shows indexing/map working by building an API link, but how do I get the whole API call and message to be inside the indexing so each list item is called and returned by the API:
// {"342671641006047252":["MSFT","AMZN","CVNA","TEAM"]}
console.log(list);
// This is indexing through the list and bulding the link
const tickers = list
.map((ticker, index) => `https://financialmodelingprep.com/api/v3/quote/${ticker}?apikey=6c7ee1f3c7a666228979fa0678fa22a3`)
return message.channel.send(tickers)
// This is going to the api for list[0]
axios.get('https://financialmodelingprep.com/api/v3/quote/'+list[0]+'?apikey=6c7ee1f3c7a666228979fa0678fa22a3').then(resp => {
console.log(resp.data);
let symbol = resp.data[0].symbol;
let price = resp.data[0].price;
let changesPercentage = resp.data[0].changesPercentage;
return message.channel.send({embed: {
color: 8311585,
fields: [{
name: "Ticker",
value: `${symbol}`,
inline: "true"
},
{
name: "Price",
value: `${price}`,
inline: "true"
},
{
name: "Change %",
value: `${changesPercentage}`,
inline: "true"
},
Nevermind on this one! I used forEach instead of .map and got it working!

Source State record to sublist field

I am trying to create a custom sublist with sublist field with source to be States record that is managed in Setup > Company > States/Provinces/Countries section.
Here is the example code that I am using and it doesn't work.
_sublist.addField({
id: 'custpage_license_state,
type: serverWidgetModule.FieldType.SELECT,
label: 'LICENSE STATE',
source: 'state' //not recognizing record id
});
I have tried using 'state', 'states', '-195', -195 (was able to locate that this is the internal id for states record in our instance "-195"), but nothing works.
Does anybody has an idea on how to make that work.
Thanks.
The State/Province record isn't exposed. You'll need to add the options to the field manually. You could either perform a search against the customer records which will only return states currently assigned;
/**
* Gets customers geographical states.
*
* #returns {Array} of state information.
*/
function getStates() {
var records = [];
var customerSearchObj = search.create({
type: "customer",
filters: [
["formulatext: {country}", "isnotempty", ""],
"AND",
["formulatext: {state}", "isnotempty", ""]
],
columns: [
search.createColumn({
name: "statedisplayname",
summary: "GROUP",
sort: search.Sort.ASC
}),
search.createColumn({ // abbreviation
name: "state",
summary: "GROUP"
})
]
});
customerSearchObj.run().each(function (result) {
var rec = {
state: result.getValue({name: 'state', summary: 'GROUP'}),
stateDisplay: result.getValue({name: 'statedisplayname', summary: 'GROUP'})
};
records.push(rec);
return true;
});
return records;
}
Or, create a customer in memory and then get the states; (Sorry, SS1 code, taken from SA 63293.)
function getAllStatesForCountry() {
var customer_record = nlapiCreateRecord('customer', {recordmode: 'dynamic'});
customer_record.selectLineItem('addressbook', 1);
var addrSubrecord = customer_record.createCurrentLineItemSubrecord('addressbook', 'addressbookaddress');
addrSubrecord.setFieldValue('country', 'GB');
var stateField = addrSubrecord.getField('dropdownstate');
return stateField.getSelectOptions();
}
And then loop through the result and add them to your field using mySelect.addSelectOption().

Replacing an object in an object array in Redux Store using Javascript/Lodash

I have an object array in a reducer that looks like this:
[
{id:1, name:Mark, email:mark#email.com},
{id:2, name:Paul, email:paul#gmail.com},
{id:3,name:sally, email:sally#email.com}
]
Below is my reducer. So far, I can add a new object to the currentPeople reducer via the following:
const INITIAL_STATE = { currentPeople:[]};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case ADD_PERSON:
return {...state, currentPeople: [ ...state.currentPeople, action.payload]};
}
return state;
}
But here is where I'm stuck. Can I UPDATE a person via the reducer using lodash?
If I sent an action payload that looked like this:
{id:1, name:Eric, email:Eric#email.com}
Would I be able to replace the object with the id of 1 with the new fields?
Yes you can absolutely update an object in an array like you want to. And you don't need to change your data structure if you don't want to. You could add a case like this to your reducer:
case UPDATE_PERSON:
return {
...state,
currentPeople: state.currentPeople.map(person => {
if (person.id === action.payload.id) {
return action.payload;
}
return person;
}),
};
This can be be shortened as well, using implicit returns and a ternary:
case UPDATE_PERSON:
return {
...state,
currentPeople: state.currentPeople.map(person => (person.id === action.payload.id) ? action.payload : person),
};
Mihir's idea about mapping your data to an object with normalizr is certainly a possibility and technically it'd be faster to update the user with the reference instead of doing the loop (after initial mapping was done). But if you want to keep your data structure, this approach will work.
Also, mapping like this is just one of many ways to update the object, and requires browser support for Array.prototype.map(). You could use lodash indexOf() to find the index of the user you want (this is nice because it breaks the loop when it succeeds instead of just continuing as the .map would do), once you have the index you could overwrite the object directly using it's index. Make sure you don't mutate the redux state though, you'll need to be working on a clone if you want to assign like this: clonedArray[foundIndex] = action.payload;.
This is a good candidate for data normalization. You can effectively replace your data with the new one, if you normalize the data before storing it in your state tree.
This example is straight from Normalizr.
[{
id: 1,
title: 'Some Article',
author: {
id: 1,
name: 'Dan'
}
}, {
id: 2,
title: 'Other Article',
author: {
id: 1,
name: 'Dan'
}
}]
Can be normalized this way-
{
result: [1, 2],
entities: {
articles: {
1: {
id: 1,
title: 'Some Article',
author: 1
},
2: {
id: 2,
title: 'Other Article',
author: 1
}
},
users: {
1: {
id: 1,
name: 'Dan'
}
}
}
}
What's the advantage of normalization?
You get to extract the exact part of your state tree that you want.
For instance- You have an array of objects containing information about the articles. If you want to select a particular object from that array, you'll have to iterate through entire array. Worst case is that the desired object is not present in the array. To overcome this, we normalize the data.
To normalize the data, store the unique identifiers of each object in a separate array. Let's call that array as results.
result: [1, 2, 3 ..]
And transform the array of objects into an object with keys as the id(See the second snippet). Call that object as entities.
Ultimately, to access the object with id 1, simply do this- entities.articles["1"].
If you want to replace the old data with new data, you can do this-
entities.articles["1"] = newObj;
Use native splice method of array:
/*Find item index using lodash*/
var index = _.indexOf(currentPeople, _.find(currentPeople, {id: 1}));
/*Replace item at index using splice*/
arr.splice(index, 1, {id:1, name:'Mark', email:'mark#email.com'});

Formatter parameter passes as null

I am trying to export table data to an Excel sheet. Everything works OK without formatter. But I have to format some cells before converting table to Excel. I'm debugging the code. Formatter function's parameter passes as null value. Here is my code:
var oExport = new sap.ui.core.util.Export({
exportType: new sap.ui.core.util.ExportTypeCSV({
separatorChar: ";"
}),
models: this.getView().getModel(),
rows: {
path: "/FaaliyetServisiSet"
},
columns: [{
name: "Kişi",
template: {
content: "{Klnad}"
}
}, {
name: "Faaliyet",
template: {
content: "{Falyt}"
}
}, {
name: "Süre",
template: {
content: {
parts: ["Sure"],
formatter: function(oValue) { // oValue is null that's the problem !!!!!!!
oValue = oValue + 2;
return oValue;
}
}
}
}, {
name: "Proje",
template: {
content: "{Proje}"
}
},
]
});
You should an array of objects instead of strings for the parts
parts: [
{ path: "Sure" }
]
https://openui5.hana.ondemand.com/#docs/guide/07e4b920f5734fd78fdaa236f26236d8.html
I´m facing a similar issue in some data binding use cases. The formatter function gets called with the initial data binding value which can be null or undefined. I circumvent this issue by ignoring calls of the formatter function by simple null and undefined checks.

Resources