i am using object literal as an alternative to if else/switch statements. In doing so not knowing how to assign same value to different keys.
What i am trying to do?
Based on variable named "user" should redirect to different links.
if value of "user" is admin or manager should redirect to say "www.exampledomain.com". If value of "user" is "purchaser" should redirect to "https://stackoverflow.com".
To do so i have used object literal instead of if-else which is clumsy.
Below is the code,
get user() {
return ( {
'admin': 'www.exampledomain.com',
'manager': 'www.exampledomain.com',
'purchaser': 'https://stackoverflow.com',
} )[user];}
As you see from above code, admin and manager keys point to same url "www.exampledomain.com". Is there a way to assign it something like below.
get user() {
return ( {
'admin': 'manager': www.exampledomain.com',
'purchaser': 'https://stackoverflow.com',
} )[user];}
Could somebody help me solving this. Thanks.
Personally, I don't see any reason to use the second idea, furthermoe it is not valid JS code.
If you are trying to reduce code duplication you can just extract your urls into a constant variable like that.
static get REDIRECT_URLS() {
return {
"PRIMARY_SITE" : "www.exampledomain.com",
"SECONDARY_SITE" : "stackoverflow.com",
};
}
get user() {
return ( {
'manager' : FooClass.REDIRECT_URLS.PRIMARY_SITE,
'admin': FooClass.REDIRECT_URLS.PRIMARY_SITE,
'purchaser': FooClass.REDIRECT_URLS.SECONDARY_SITE,
} )[user];}
Of course there are other possible solutions, like having keys like 'admin|manager' : "url", but that doesn't seem to be a good choice and you need to add extra logic to iterate over the object keys and check if a key matched the regex.
If the problem were to be viewed in isolation, I would solve this issue by simply flipping the data structure around
const linkToTypeMapping = {
'www.exampledomain.com': ['admin', 'manager'],
'https://stackoverflow.com': ['purchaser'],
}
But that doesn't really fix your issue.
The way I would solve the actual use-case is to simply add a link property to your user object and just populate and later access userObject.link.
However for completeness's sake, here's how you would extract a user's link from the data structure I posted above.
const get_link = (type) => {
for (let key in linkToTypeMapping) {
if(linkToTypeMapping.includes(type)) {
return key;
}
}
}
This is obviously very complicated as far as the code goes, but if your linkToTypeMapping object is expected to become fairly large, this might actually be the right solution for you.
You can make default route like that:
function user() {
const defaultRoute = "www.exampledomain.com"
return ({
'admin': defaultRoute,
'manager': defaultRoute,
'purchaser': 'https://stackoverflow.com'
})[user];
}
or that
function user2() {
const defaultRoute = "www.exampledomain.com"
return ({
'someoneElse': "www.google.com",
'purchaser': 'https://stackoverflow.com'
})[user] || defaultRoute;
}
or mix both styles, depending on how complex your statement is;
or even make it from the other side
function user3(user) {
const routes = {
"www.google.com": ["admin", "manager"],
"www.exampledomain.com": ["purchaser"]
};
return Object.keys(routes).find(k => routes[k].includes(user));
}
console.log(user3('manager')); //www.google.com
Related
I have a js file that looks for paths and rewrites the paths, and it has over 300 if/else statements. I am looking for ways to clean it up, but I also need to make sure that it is readable for the next person who will work on it. My first thought was to convert all of these if statements to either ternaries or switch statements, but I am not sure if that is the best approach. I am trying to balance the urge to write "better" code, but also I need to make sure it stays readable, and the current if/else structure reads pretty well.
Example:
if (request.uri.match(^\/path1\/.*$)) {
request.uri = request.uri.replace(/^\/path1\/(.*)$/) "newPath" + "$1");
} else if (request.uri.match(^\/path2\/.*$)) {
request.uri = request.uri.replace(/^\/path2\/(.*)$/) "newPath" + "$1");
} else if (request.uri.match(^\/path3\/.*$)) {
request.uri = request.uri.replace(/^\/path3\/(.*)$/) "newPath" + "$1");
}
I would say about 75% of the 300 if statements look similar. The path1, path2 examples are overly simplistic, the regex for each of these range from simple to complex. The other 25% incorporate other logic aside from just rewriting the URL. Think injecting headers, or in some cases one more level of nested if/else statements.
Here is an example of what some of those might look like:
else if (request.uri.match(^\/path3(-new)?\/.*$)) {
request.headers['header'] = {"value": "something"};
if (request.uri.match(^\/path3\/.*$)) {
request.uri = request.uri.replace(/^\/path3\/(.*)$/) "/newPathA/" + "$1");
} else if (request.uri.match(^\/path3-new\/.*$)) {
request.uri = request.uri.replace(/^\/path3-new\/(.*)$/) "/newPathB/" + "$1");
}
}
I went down the path to build functions to handle different things, like the uri replace lines. The challenge is the code that the functions are replacing aren't difficult, and it just feels like a bit of a waste. I can either have a line that does this:
request.uri = request.uri.replace(/^\/path3-new\/(.*)$/) "/newPathB/" + "$1");
Or I can have this:
function pathModifier(newPath) {
request.uri = reqUri.replace(/^(\/.*?[\/])(.*)$/, newPath);
return;
}
pathRewriter("/newPathA/" + "$1");
I am not saving a lot, and it makes it more difficult to read for the person I pass this on to.
So, what would you do?
I'd use a declarative approach for the majority of cases and still expose a way to extend it by passing a custom function.
This way you have low amounts of repetition in your code while still offering the needed flexibility.
Something like this:
const rewrites = {
'/path1/': ['/newPathA/'],
'/path2/': ['/newPathB/'],
'/path3/': ['/newPathC1/', { headers: { name: 'value' } }],
'/path3-new/': ['/newPathC2/', { headers: { name: 'value' } }],
'/path4': ['/newPathD', { headers: { name: 'value' }, callback: req => {
req.customProperty = 123
req.doCustomStuff()
} }]
}
function handleRewrites (req, rewrites) {
const [newPath, options] = Object.keys(rewrites)
.find(oldPath => req.uri.startsWith(oldPath)) ?? []
if (newPath) {
req.uri = newPath + req.uri.slice(oldPath.length)
if (options?.headers) Object.assign(req.headers, options.headers)
options?.callback?.(req)
}
}
Of course you are free to design the "domain-specific language" of how to declare the rewrites as it best fits your use case. I chose an object with old path as key and [newPath, optionalOptionsObject] as value because the majority of the time I'd expect there to be just an oldPath=>newPath transformation but it could be different of course.
If you would need for example actual regular expressions in some cases, you could of course change it and do something like an array where you then use values of the format [oldPathOrRegex, newPath, optionalOptionsObject]. You could then allow the easy string path but also allow regexes (depending on whether it is instanceof RegExp you'd apply one logic or the other, allowing you to specify both types of path selectors with little effort when declaring the rewrites).
There is of course still the thing that you would specify the common headers of several routes multiple times. One of the possible ways to allow specifying a (possibly even nested) group of rewrites with common properties that get merged with any per-route properties would be creating a function that takes the common settings plus another rewrite list and returns an expanded version of each given rewrite, and to then spread the result of the function with ...:
function withOptions (commonOptions, rewrites) {
for (const rewrite of Object.values(rewrites)) {
if (!rewrite[1]) rewrite[1] = {}
const rewriteOptions = rewrite[1]
if (commonOptions.headers || rewriteOptions.headers) {
rewriteOptions.headers = {
...commonOptions.headers,
...rewriteOptions.headers
}
}
if (commonOptions.callback || rewriteOptions.callback) {
const childCallback = rewriteOptions.callback
rewriteOptions.callback = req => {
commonOptions.callback?.(req)
childCallback?.(req)
}
}
}
return rewrites
}
const rewrites = {
'/path1/': ['/newPathA/'],
'/path2/': ['/newPathB/'],
...withOptions({ headers: { name: 'value' } }, {
'/path3': ['/newPathC2/'],
'/path3-new': ['/newPathC3/']
}),
'/path4': ['/newPathD', { headers: { name: 'value' }, callback: req => {
req.customProperty = 123
req.doCustomStuff()
} }]
}
Note that in this example I wrote withOptions so it mutates the given rewrites, because it was the simplest way and I assumed it would be passed object literals anyway. If this isn't true, it can be changed to return copies of course.
If you have many cases, I would suggest using a HashMap/Object, to map between the lookup key and their value.
const paths = {
'/old/path/to/a': '/new/path/to/a',
'/old/path/to/b': '/new/path/to/b',
'/old/path/to/c': '/new/path/to/c'
}
function getNewPath(oldPath) {
return paths[oldPath] || '/fallback/not/found'
}
Or if you have to handle different business logic, you could also return a function
const paths = {
'/old/path/to/a': () => { return modifyA() },
'/old/path/to/b': () => { return modifyB() },
'/old/path/to/c': () => { return modifyC() },
}
function getNewPath(oldPath) {
return paths[oldPath]()
}
EDIT:
After trying many different approaches i found a working solution that maps any object to a format that mongoose understands. See solution here: https://stackoverflow.com/a/69547021/17426304
const updateNestedObjectParser = (nestedUpdateObject) => {
const final = {
}
Object.keys(nestedUpdateObject).forEach(k => {
if (typeof nestedUpdateObject[k] === 'object' && !Array.isArray(nestedUpdateObject[k])) {
const res = updateNestedObjectParser(nestedUpdateObject[k])
Object.keys(res).forEach(a => {
final[`${k}.${a}`] = res[a]
})
}
else
final[k] = nestedUpdateObject[k]
})
return final
}
ORIGINAL QUESTION:
I have a mongoose structure of
ChildSchema = {
childProperty: String,
childProperty2: String
}
MainSchema = {
mainProperty: String,
mainProperty2: String,
child: childSchema
}
In my update function I want to pass a partial object of mainSchema and only update the properties I pass to the function.
This works fine for direct properties on my mainSchema but not on my childSchema. It overwrites the whole child property with the partial object given by my request.
So my update object looks something like this
const updates = {
child: {
childProperty2: 'Example2'
}
}
How can I only update the childProperty2 without deleting the childProperty?
In this example it would be easy to just update every property alone but the real world objects are much bigger and can be nested into multiple levels.
I tried to use destructuring but it does not seem to work
const example = MainSchema.findOne({_id})
if (updates.child) example.child = {...example.child, ...updates.child} // Does not work
Is there a solution to that in mongoose (6.0)?
Change your code like this:
const updates = {
"child.childProperty2": 'Example2'
}
This is pretty simple, I'm trying to insert a value that has a string as a parameter.
Let's give an example:
const stringName = "User".
and my goal is to activate a function that gonna be called like that
User.find();
I tried the follows without success: stringName.find() but didn't worked and also [stringName].find()
Why I want to do that: I receive a param that is a string, and I want to search with this param in my DB so the real query gonna be something like that: param.findAll({where....})
I'm sure that there is a simple solution like the examples above. hope for help.
You can store User in an object, and then access it with the string.
const myObjects = {
User: User,
// Post: Post, ...
}
const stringName = "User"
myObjects[stringName].find('foo')
You probably could do something like this:
let fncName(clsName) {
if(window[clsName]) {
let cls = new window[clsName]();
cls.find();
}
}
This however requires all classes that you are calling to have a function named find. Probably better to do a switch on the passed in string and go from there.
let fncName(clsName) {
switch(clsName) {
case 'User':
User.find();
default:
...
}
}
You can't simply execute .find() on a string unless you define the find() method in string prototype.
If your goal is to execute some methods of an object, and that object's name you are getting as a string.
If that object is available in your current context:
const param = "User";
this[param].find()
If object is not available in current context:
let objectMap = {
"user": USER
};
let param = "user";
objectMap[param].find();
Currently, I have a select element in my html which has a ngModel to the object details:
[ngModel]="details?.publicInformation?.firstname"
However, publicInformation may not exist in that object, or if it does, maybe firstname does not exist. No matter the case, in the end, I want to create the following:
[ngModel]="details?.publicInformation?.firstname" (ngModelChange)="details['publicInformation']['firstname'] = $event"
Basically, if the select is triggered, even if neither of publicInformation nor firstname exist, I would like to create them inside details and store the value from the select.
The issue is that I am getting
Cannot set property 'firstname' of undefined
Can someone explain what I am doing wrong here and how can I achieve the result I desire?
You need to initialize details and publicInformation to empty object
public details = {publicInformation : {}};
You should do that when you load the form data.
For example, you might have something like this:
ngOnInit() {
this._someService.loadForm().then((formData: FormData) => {
this.details = formData;
});
}
Then, you could modify that to fill in the missing empty properties you need:
ngOnInit() {
this._someService.loadForm().then((formData: FormData) => {
this.details = formData || {};
if (!this.details.publicInformation) {
this.details.publicInformation = { firstname: '' };
} else if (!this.details.publicInformation.firstname) {
this.details.publicInformation.firstname = '';
}
});
}
However, it would be better to place this logic in the services, so that they are responsible for adding all the necessary empty properties to the data they load, or if you are using Redux, then it should go into the reducers.
I use the following code which is working great but I wonder if in JS there is a way to avoid the if and to do it inside the loop, I want to use also lodash if it helps
for (provider in config.providers[0]) {
if (provider === "save") {
....
You can chain calls together using _.chain, filter by a value, and then use each to call a function for each filtered result. However, you have to add a final .value() call at the end for it to evaluate the expression you just built.
I'd argue that for short, simple conditional blocks, an if statement is easier and more readable. I'd use lodash- and more specifically chaining- if you are combining multiple operations or performing sophisticated filtering, sorting, etc. over an object or collection.
var providers = ['hello', 'world', 'save'];
_.chain(providers)
.filter(function(provider) {
return provider === 'save';
}).each(function(p) {
document.write(p); // your code here
}).value();
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.8.0/lodash.js"></script>
Edit: My mistake; filter does not have an overload where you can just supply a literal value. If you want to do literal value checking you have to supply a function as in my amended answer above.
I'd argue that what you have there is pretty good, clean and readable, but since you mentioned lodash, I will give it a try.
_.each(_.filter(config.providers[0], p => p === 'save'), p => {
// Do something with p
...
});
Note that the arrow function/lambda of ECMAScript 6 doesn't come to Chrome until version 45.
Basically, you are testing to see if config.providers[0], which is an object, contains a property called save (or some other dynamic value, I'm using a variable called provider to store that value in my example code below).
You can use this instead of using a for .. in .. loop:
var provider = 'save';
if (config.providers[0][provider] !== undefined) {
...
}
Or using #initialxy's (better!) suggestion:
if (provider in config.providers[0]) {
...
}
How about:
for (provider in config.providers[0].filter(function(a) {return a === "save"}) {
...
}
Strategy, you are looking for some kind of strategy pattern as,
Currenlty the save is hardcoded but what will you do if its coming from other varible – Al Bundy
var actions = {
save: function() {
alert('saved with args: ' + JSON.stringify(arguments))
},
delete: function() {
alert('deleted')
},
default: function() {
alert('action not supported')
}
}
var config = {
providers: [{
'save': function() {
return {
action: 'save',
args: 'some arguments'
}
},
notSupported: function() {}
}]
}
for (provider in config.providers[0]) {
(actions[provider] || actions['default'])(config.providers[0][provider]())
}
Push „Run code snippet” button will shows two pop-ups - be carefull
It is not clearly stated by the original poster whether the desired output
should be a single save - or an array containing all occurrences of
save.
This answer shows a solution to the latter case.
const providers = ['save', 'hello', 'world', 'save'];
const saves = [];
_.forEach(_.filter(providers, elem => { return elem==='save' }),
provider => { saves.push(provider); });
console.log(saves);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.19/lodash.js"></script>