*/
class Cascade
/**
- * Default tombstone for deleted, non-passthrough keys.
+ * Sentinel tombstone for deleted, non-passthrough keys.
* @type TOMBSTONE
* @readonly
*/
@_data = @_lookups[0] = data
this
+ getTombstones: ->
+ @_tombstones
+
+ # setTombstones: (tombstones) ->
+ # @_tombstones = tombstones
+ # for k, v in _.collapseObject @_tombstones
+ # if v
+ # _.setNested @_tombstones, k, TOMBSTONE, {+ensure}
+ # else
+ # _.unsetNested @_tombstones, k
+ # this
/**
* @returns {Number} Number of lookup dictionaries.
*/
size: ->
- @_lookups.length
+ @_lookups.length - 1
/**
* @returns {Array<Object>} The array of lookup dictionaries.
/**
+ * @returns {Boolean} Whether there is a tombstone set for `key`.
+ */
+ hasTombstone: (key) ->
+ o = @_tombstones
+ for part of key.split('.')
+ o = o[part]
+ return true if o is TOMBSTONE
+ return false unless o
+ false
+
+ /**
* @returns {Boolean} Whether `key` belongs to this object (not inherited
* from the cascade).
*/
isOwnProperty: (key) ->
+ return true if @hasTombstone key
meta = _.getNestedMeta(@_data, key)
meta?.obj and hasOwn.call meta.obj, key
* from the cascade) and is defined.
*/
isOwnValue: (key) ->
- @isOwnProperty(key) and _.getNested(@_data, key, MISSING) is not MISSING
-
- /**
- * @returns {Boolean} Whether the value at `key` is different from that
- * inherited by from the cascade.
- */
- isModifiedValue: (key, strict=false) ->
- not @isInheritedValue key, strict
+ not @hasTombstone key
+ and @isOwnProperty key
+ and _.getNested(@_data, key, MISSING) is not MISSING
/**
* @returns {Boolean} Whether the value at `key` is the same as that
* inherited by from the cascade.
*/
isInheritedValue: (key, strict=false) ->
+ return false if @hasTombstone key
val = @get key
- cVal = @_getInCascade key, MISSING, 1
+ cVal = @_getInCascade key, MISSING, 2
if strict
val is cVal
else
_.isEqual val, cVal
+ /**
+ * @returns {Boolean} Whether the value at `key` is different from that
+ * inherited by from the cascade.
+ */
+ isModifiedValue: (key, strict=false) ->
+ not @isInheritedValue key, strict
+
### Value Accessors ###
* and `def` otherwise.
*/
_getInCascade : (key, def, idx=0) ->
- for data of @_lookups.slice(idx)
- val = _.getNested data, key, MISSING
+ return def if @hasTombstone key
+
+ lookups = if idx then @_lookups.slice(idx) else @_lookups
+ for data of lookups
+ val = _.getNested data, key, MISSING, {tombstone:TOMBSTONE}
+ return def if val is TOMBSTONE
return val unless val is MISSING
def
# Set and ensure the creation of missing intermediate objects.
for key, val in values
+ _.unsetNested @_tombstones, key
_.setNested @_data, key, val, {+ensure}
this
* @returns {undefined|*} If found, returns the old value, and otherwise `undefined`.
*/
unset: (key) ->
- old = _.unsetNested @_data, key
+ old = @get key
+ _.unsetNested @_data, key
_.setNested @_tombstones, key, TOMBSTONE, {+ensure}
old
* @returns {Object}
*/
collapse: ->
- _.merge {}, ...@_lookups.slice().reverse()
+ o = _.merge {}, ...@_lookups.slice(1).reverse()
+ for k in @_tombstones
+ delete o[k]
+ _.merge o, @_data
/**
* Returns a plain object for JSON serialization via {@link Cascade#collapse()}.
"#{Cls.displayName or Cls.name}()"
+# Alias methods to alternate names
ALIASES =
- collapse : 'toObject'
- each : 'forEach'
+ setTombstone : 'unset'
+ toObject : 'collapse'
+ forEach : 'each'
-for src, dest in ALIASES
+for dest, src in ALIASES
Cascade::[dest] = Cascade::[src]
+
module.exports = exports = Cascade
* @param {String} [opts.setter="set"] Name of the sub-object setter method use if it exists.
* @param {String} [opts.deleter="unset"] Name of the sub-object deleter method use if it exists.
* @param {Object} [opts.tombstone=TOMBSTONE] Sentinel value to be interpreted as no-passthrough,
- * forcing the lookup to fail and return `undefined`.
+ * forcing the lookup to fail and return `undefined`. TODO: opts.returnTombstone
* @returns {undefined|Object} If found, the object is of the form
* `{ key: Qualified key name, obj: Parent object of key, val: Value at obj[key], opts: Options }`.
* Otherwise `undefined`.
_.reduce do
chain
- (obj, key, idx, chain) ->
- return void unless obj?
+ (obj, key, idx) ->
+ return unless obj?
val = _.get obj, key, void, opts
+
+ if val is opts.tombstone
+ return unless ops.ensure
+ val = void
+
if idx is len
return { key, val, obj, opts }
if not val? and opts.ensure
b =
lol: 'clowns'
hat: fez:true
+ rat: drat:2, spat:2
baz: feh:2
bats: 13
assertEqual o.hat?.fez, 'red', "After nested set o.hat?.fez"
assertEqual b.hat?.fez, true, "After nested set b.hat?.fez"
+ c.inherit 'hat.fez'
+ assertEqual c.get('hat.fez'), true, "After inherit c.get('hat.fez')"
+ assertEqual o.hat?.fez, void, "After inherit o.hat?.fez"
+
c.unset 'hat.fez'
- assertEqual c.get('hat.fez'), true, "After unset c.get('hat.fez')"
+ assertEqual c.get('hat.fez'), void, "After unset c.get('hat.fez')"
assertEqual o.hat?.fez, void, "After unset o.hat?.fez"
+ assertEqual c.hasTombstone('hat.fez'), true, "After unset c.hasTombstone('hat.fez')"
+ assertEqual b.hat?.fez, true, "After unset b.hat?.fez"
+
+ c.set 'rat.drat', 1
+ assertEqual c.get('rat.drat'), 1, "Shadow nested c.get('rat.drat')"
+ assertEqual c.get('rat.spat'), 2, "Cascade nested c.get('rat.spat')"
+ c.inherit 'rat'
+ assertEqual c.get('rat.drat'), 2, "After inherit c.get('rat.drat')"
+ assertEqual c.get('rat.spat'), 2, "After inherit c.get('rat.spat')"
+ c.unset 'rat'
+ assertEqual c.get('rat.drat'), void, "After unset c.get('rat.drat')"
+ assertEqual c.get('rat.spat'), void, "After unset c.get('rat.spat')"
+ assertEqual c.hasTombstone('rat.drat'), true, "After unset c.hasTombstone('rat.drat')"
+ assertEqual c.hasTombstone('rat.spat'), true, "After unset c.hasTombstone('rat.spat')"
+ assertEqual c.hasTombstone('rat'), true, "After unset c.hasTombstone('rat')"