-
Notifications
You must be signed in to change notification settings - Fork 28
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
Consider using _.merge (or similar pattern) for nested options instead of _.extend #91
Comments
@jonathanolson can you elaborate on your point? I don't understand it. |
So the main issue moving forward is: we like the recursion, but we need control about how deep the recursion should go? And for Maybe if we invent our own |
This seems like a nice pattern. I'm starting to lean towards a options = Merge.merge( { defaults }, options );
// or
options = new Merge( defaults, options );
///////
options.getFlat() // nested keys omitted
/* or something like */
noSubOptions = Merge.removeNestedOptions( options ); |
To evaluate the original example in #91 (comment), we should see what it looks like with our current function MyNodeTypeWithHSliderInIt( options ) {
options = _.merge( {
visible: false,
pickable: false,
hSliderOptions: null // nested options passed to HSlider, defaults set below
}, options );
options.hSliderOptions = _.extend( {
endDrag: function() { ... },
startDrag: function() { ... }
}, options.hSliderOptions, );
var slider = new HSlider( new Property(), new Range(), options.hsliderOptions );
} |
I'll definitely clarify any potential objections during the dev meeting, but it seems workable to only recurse into keys that end in constructor( options ) {
options = merge( {
visible: false,
pickable: false,
hSliderOptions: {
startDrag: () => {},
endDrag: () => {}
}
}, options );
const slider = new HSlider( new Property(), new Range(), options.hsliderOptions );
} where we would use our own import ( |
Another element to consider is that when nested options are used in a base type, the pattern @pixelzoom illustrated in #91 (comment) has to be propagated to subtypes and potentially suptype instantiations. For example, a sim may create a type that uses function MyNodeThatNeedsNestedOptions( options ) {
options = _.merge( {
visible: false,
pickable: false,
nodeWithHSliderInItOptions: null
}, options );
var nodeWithHSliderInItOptions = _.extend( {
hSliderOptions: null,
maxWidth: 44,
...
}, options.nodeWithHSliderInItOptions );
nodeWithHSliderInItOptions.hSliderOptions = _.extend( {
endDrag: function() { ... },
startDrag: function() { ... }
}, nodeWithHSliderInItOptions.hSliderOptions );
var nodeWithHSliderInIt = new MyNodeTypeWithHSliderInIt( new Property(), new Range(), nodeWithHSliderInItOptions );
} this also doesn't show the issue where multiple instances of |
2/14/19 dev meeting consensus:
|
I've added the merge function and a large number of unit tests, but it's possible I've missed some cases to check. Thus far, it seems to be working well but explicitly checking the key for 'Options' as well as testing that it's prototype is the same as someNestedOptions: function() {
return {
key: 'value'
};
} that will currently fail, too. One departure from const o1 = {
subOptions: {
arr: [
{
moreOptions: {
key1: 'val',
key2: 'val2'
}
},
{ key4: 'val4' }
]
}
};
const o2 = {
subOptions: {
arr: [
{
moreOptions: {
key1: 'new val',
key3: 'another'
}
},
{ key3: 'value3' }
]
}
};
const o3 = merge( o1, o2 ); lodash's implementation would result in o3 = {
subOptions: {
arr: [
{
moreOptions: {
key1: 'new val',
key2: 'val2',
key3: 'another'
}
},
{
key4: 'val4',
key3: 'value3'
}
]
}
} and is dependent on the order in the array. It seems safer to me to treat arrays as other values. If a developer needs combine arrays within options, I think that should be handled outside of merge/extend. |
I have case in Wave Interference where it would be appropriate to use merge (I think), however, it seems like merge is mutating the arguments. It's unclear whether this is desirable. For instance: const a = {
sliderOptions: {
hello: 'there'
}
};
const b = {
sliderOptions: {
time: 'now'
}
};
merge( {}, a, b ); results in
Is that as expected? What I'm seeing in Wave Interference is that I'm not sure whether I should tinker with |
function merge( obj ) {
...
return obj;
} That violates best practices for options (see #96):
... and is likely why |
On the other hand, maybe
|
Hmmm... @samreid originally had something like:
... and reported that |
Are the unit tests in mergeTests.js adequate? Do they verify that objects that should not be modified are unchanged after a merge? |
I had this code: super( title, property, property.range, merge( {
sliderOptions: {
majorTicks: [ {
// TODO: model coordinates for these
value: property.range.min,
label: new WaveInterferenceText( minLabel )
}, {
value: property.range.max,
label: new WaveInterferenceText( maxLabel )
} ]
}
}, NUMBER_CONTROL_OPTIONS, options ) ); And it was modifying }, options, NUMBER_CONTROL_OPTIONS ) ); Then it works correctly, but options gets mutated (and order of application/precedence is unclear). |
One more point for code review. We discussed merging for keys that end with |
Documentation for |
All args expect the last one are modified because
This is a problem. And yes, it means the unit tests are inadequate. Recommended not to use |
Hmm... Or again, is this intended behavior? Hard to tell without documentation. I'm not going to do any more work on this until @mbarlow12 has had a chance to chime in. |
I expected behavior like > var a = { a: 1, b: 2 }
> var b = { c: 3, d: 4 }
> _.extend( a, b, { e: 5, f: 6 } )
> a
{a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 }
> b
{c: 3, d: 4} |
@zepumph volunteered to review merge and its usages and unit tests. That will put us in a better position to understand what the next steps are. |
Also, in review @chrisklus and @zepumph and I looked at code with |
…ation function, use var args instead of `arguments`, use unabbreviated var names, add tests for args and non literal objects, phetsims/phet-info#91
From the review:
options = _.extend( {
xOptions: null // filled in below
. . .
}, options );
options = merge( {
xOptions: { test: 1 }
}, options ); Since xOptions, is null it fails the type check validation. I ran into this in a pattern in
UPDATE: I just had the idea to add
Review is complete. It would be nice to discuss these points out with someone, though I'm not really sure who. We could discuss at developer meeting but that seems more costly than needed. @ariel-phet will you please assign someone to look over my review before bringing this to developer meeting. |
In the above commit I added support for optional options objects to be passed as sources. I also added tests for that. Basically it works by ignoring any source that is |
From developer meeting today...
Thanks all! I will implement these, and then mark for dev meeting again as a "ready for mass consumption" PSA. |
Alright the above has been implemented. As per the discussion in developer meeting from August 8th, merge is ready for mass consumption. Marking on developer meeting as a PSA. Hopefully we can discuss how this function plays into our options strategy as part of the options/config design pattern discussion coming up. |
Discussed at dev meeting: 09/19/19 Merge() is fully operational and we can decide how to use this in the next software design pattern meeting. Assigning to @jbphet to read through and close. |
Thanks. I've read through and ingested the information, and we can finalize how PhET uses |
After an unexpectedly lengthy thread in Slack, it seemed like this was worth discussing at the dev meeting.
While working on phetsims/scenery-phet#451 and implementing the nested options pattern outlined in https://github.com/phetsims/phet-info/blob/master/doc/phet-software-design-patterns.md#nesting, I realized that Lodash has a
merge
function that has similar functionality to_.extend
though can also extend nested objects.If the client overwrites
hsliderOptions.startDrag
and addshsliderOptions.ariaValueTextPattern = somePatternString
, those changes will all be correctly passed to the HSlider constructor without an additional call to_.extend
.However, 2 concerns arise from this function:
extend
, so if a reference to a default object were passed in (_.merge( defaultOptions, otherOptions )
),defaultOptions
would have the new/updated keys added. From Slack, @samreid noted that this can be solved with_.extend
,_.merge
performs a deep copy of the objects. Developers noted that this is a problem for situations like Enumerations where the actual reference is important.@jonathanolson
or
So there are
@samreid mentioned
@jonathanolson
The text was updated successfully, but these errors were encountered: