-
-
Notifications
You must be signed in to change notification settings - Fork 17k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added app.extend() #3568
Added app.extend() #3568
Conversation
Hey @wesleytodd I see no issues migrating One example would be if you needed to update stored configurations for a particular service while keeping to express's familiar set/get/expand* methods:
Maybe @brunoscopelliti could also share his use case for needing |
In some cases I found useful to set related fields under the same object. app.set("log_level", "verbose");
app.set("log_active", true);
// vs
app.set("log_config", { level: "verbose", active: true }); When I use the latter approach I would like to have a better method to update a single field, than: app.set("log_config", Object.assign({}, app.get("log_config"), { dir: "/db/log/" })); |
Hey @boycce, first off, I hope those are not your production configs ;). But second @boycce & @brunoscopelliti, your use cases seem reasonable. I prefer I think this change is something which could be landed on |
Is this handling inherited settings correctly? It seems like it's actually altering the parent property in addition to setting the child property to the same object instance. I feel like the parent object should not be getting modified, so the setting on the parent app should remain the same when the setting on the child app is extended. |
I dont think it would set the parent's property because it is prototypicaly inherited, not done by reference. So if it was initially from the parent, it would just result in settings it's own property with a fully copy of the parent and not touching the parent values. I could be wrong though, would be worth having a test around this. |
I did test this and it is altering the parent property as well as setting the child property to the same object reference. |
Ahh, ok then I miss-read how this works, sorry. Yes I agree it should not modify the parent. |
Here is the test I ran. I feel like this test should pass, but it currently does not: var assert = require('assert')
var express = require('express')
var app1 = express()
var app2 = express()
app1.set('log_config', { level: "verbose", active: false })
assert.equal(app1.get('log_config').active, false)
assert.equal(app2.get('log_config'), undefined)
app1.use(app2)
assert.equal(app1.get('log_config').active, false)
assert.equal(app2.get('log_config').active, false)
app2.extend('log_config', { active: true })
assert.deepEqual(app1.get('log_config'), { level: "verbose", active: false })
assert.deepEqual(app2.get('log_config'), { level: "verbose", active: true })
console.log('success')
|
Added app.extend(), which provides an easy way to update certain properties of a setting.
`extend` shouldn't be setting the propertry to the same object reference.
Updated to 4.x and removed object referencing. |
I left a comment about the |
Added `utils.extendObject()` to support previous Node versons Removed support for extending arrays and throws an error
* @private | ||
*/ | ||
|
||
exports.extendObject = function(target, source) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of adding more code here, can the dependency utils-merge
we already have perform this function? Or if not, what's the difference between this code and utils-merge
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @dougwilson, sorry I didn't see this merge util, I was scanning for an extending extend()
function or corresponding library 🤐. This passes my tests:
this.settings[setting] = merge(merge({}, this.settings[setting]), val)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh, I didn't realize that utils-merge
did not support variadic calls. I think this is, sadly, the best way at the moment, but I think we should add to the roadmap "removing utils-merge in favor of object.assign or an equivilant fallback".
What do you think we should do for the case where Example:
|
* | ||
* app.set('foo', { 'bar' : 1 }) | ||
* app.expand('foo', { 'baz' : 1 }); | ||
* app.expand('foo'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@wesleytodd thoughts on this? I'm not sure what the use of having .extend
called with one argument returning the current value -- that's already covered with .get
and .set
I don't think we need a third method to get the value of a setting, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Not sure if it should error, but it shouldn't do anything and shouldn't be documented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be useful to throw error that required argument is missing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I will edit this later today.
if (typeof val === 'undefined') { | ||
return this; | ||
} else if (val === null || typeof val !== 'object' || val instanceof Array) { | ||
throw new Error('value needs to be an object'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be a TypeError
|
||
if (typeof val === 'undefined') { | ||
return this; | ||
} else if (val === null || typeof val !== 'object' || val instanceof Array) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would recommend Array.isArray
instead of instanceof Array
. All the instanceof Array
s were removed in Express 1.0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When looking at node.green, I think in my other comment I mentioned that I thought that wasn't supported in node 0.12
, but I think I looked at the wrong field, and it has been so long since I have used 0.12
I couldn't remember what was supported...
http://node.green/#ES2015-subclassing-Array-is-subclassable-Array-isArray-support
I think it should refuse to work on any property which is not a plain object. |
Ah, that is a good point. If a setting is an instance, calling extend here would remove the original prototype, so yea, we don't want to destroy the object. We can always add additional things to extend as semver minors so just only working on plain objects sounds like a good first implementation. |
@dougwilson, would you like me to add a I could use something like this: function isPlainObject(obj) {
return typeof obj === 'object'
&& obj !== null
&& obj.constructor === Object
&& Object.prototype.toString.call(obj) === '[object Object]'
} |
My point was more along the lines of, "don't try to merge arrays" and "don't try to handle complex types". In re-reading it I see where this led, and I think I was making it too complicated. So adding a dep for this is out of the question IMO. And there is a bunch of valid use cases for merging "object like" things, so the full app.extend = function(setting, val) {
if (val === null || typeof val !== 'object' || Array.isArray(val)) {
throw new Error('value needs to be an object');
}
debug('extend "%s" with %o', setting, val);
var currentSetting = this.settings[setting];
if (typeof currentSetting === 'undefined') {
this.settings[setting] = val;
} else {
this.settings[setting] = extendObject({}, currentSetting, val);
}
return this;
}; Thoughts? |
Sounds good by me. |
So, this pull request is a bit stale, and looking back, it doesn't seem like there was really a consensus about adding this to core express. As pointed out in the conversation above, there are certainly different extending strategies one can make... And with things like the prototype pollution issues going around and the fact that, at it's core, it's not really much more than |
Added
app.extend()
, which provides an easy way to update certain properties of a setting.Example:
Related issue #3499