Skip to content

Commit 972c928

Browse files
committed
Merge branch 'release/4.1.1'
2 parents e67cc6c + 13acd72 commit 972c928

File tree

5 files changed

+138
-39
lines changed

5 files changed

+138
-39
lines changed

.versions

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ [email protected]
2828
2929
3030
31-
local-test:space:base@4.0.2
31+
local-test:space:base@4.1.1
3232
3333
3434
@@ -52,7 +52,7 @@ [email protected]
5252
5353
5454
55-
space:base@4.0.2
55+
space:base@4.1.1
5656
5757
5858

CHANGELOG.md

+15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
Changelog
22
=========
3+
4+
## 4.1.1
5+
- Improves the way mixins are applied to classes and ensures that:
6+
- Sub classes always inherit all mixins applied to parent classes, even when this happens "later on"
7+
- All sub classes inherit the static mixin properties, even after extending the base class
8+
9+
- Adds the following helpers are added to `Space.Object` for convenience:
10+
- **static**
11+
- `MyClass.hasSuperClass()`
12+
- `MyClass.superClass([key])` - either returns the super class constructor or static property/method
13+
- `MyClass.subClasses()` - returns flat array of all sub classes
14+
- **prototype**
15+
- `myInstance.hasSuperClass()`
16+
- `myInstance.superClass([key])` - same as static version but returns prototype props/methods
17+
318
## 4.1.0
419
- Adds `hasMixin()` instance method to Space.Object to check if the class has applied or inherited a specific mixin
520

package.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package.describe({
22
summary: 'Modular Application Architecture for Meteor.',
33
name: 'space:base',
4-
version: '4.1.0',
4+
version: '4.1.1',
55
git: 'https://github.com/meteor-space/base.git',
66
documentation: 'README.md'
77
});

source/object.coffee

+57-24
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11

22
class Space.Object
33

4+
# ============= PUBLIC PROTOTYPE ============== #
5+
46
# Assign given properties to the instance
57
constructor: (properties) ->
68
@_invokeConstructionCallbacks.apply(this, arguments)
@@ -14,13 +16,26 @@ class Space.Object
1416

1517
toString: -> @constructor.toString()
1618

19+
hasSuperClass: -> @constructor.__super__?
20+
21+
# Returns either the super class constructor (if no param given) or
22+
# the prototype property or method with [key]
23+
superClass: (key) ->
24+
sup = @constructor.__super__.constructor
25+
if key? then sup.prototype[key] else sup
26+
27+
# Returns true if the passed in mixin has been applied to this or a super class
1728
hasMixin: (mixin) -> _.contains(@constructor._getAppliedMixins(), mixin)
1829

30+
# This method needs to stay separate from the constructor so that
31+
# Space.Error can use it too!
1932
_invokeConstructionCallbacks: ->
2033
# Let mixins initialize themselves on construction
2134
for mixin in @constructor._getAppliedMixins()
2235
mixin.onConstruction?.apply(this, arguments)
2336

37+
# ============= PUBLIC STATIC ============== #
38+
2439
# Extends this class and return a child class with inherited prototype
2540
# and static properties.
2641
#
@@ -128,8 +143,12 @@ class Space.Object
128143
initialize.apply(this, arguments);
129144
}')(Constructor)
130145

146+
# Add subclass to parent class
147+
Parent._subClasses.push(Child)
148+
131149
# Copy the static properties of this class over to the extended
132150
Child[key] = this[key] for key of this
151+
Child._subClasses = []
133152

134153
# Copy over static class properties defined on the extension
135154
if extension.statics?
@@ -203,18 +222,43 @@ class Space.Object
203222
else
204223
@_applyMixin(mixins)
205224

225+
# Returns true if this class has a super class
226+
@hasSuperClass: -> @__super__?
227+
206228
@isSubclassOf: (sup) ->
207229
isSubclass = this.prototype instanceof sup
208230
isSameClass = this is sup
209231
return isSubclass || isSameClass
210232

233+
# Returns either the super class constructor (if no param given) or
234+
# the static property or method with [key]
235+
@superClass: (key) ->
236+
sup = @__super__.constructor
237+
if key? then sup[key] else sup
238+
239+
# Returns a flat, uniq array of all sub classes
240+
@subClasses: ->
241+
subs = [].concat(@_subClasses)
242+
subs = subs.concat(subClass.subClasses()) for subClass in subs
243+
return _.uniq(subs)
244+
245+
# Returns true if the passed in mixin has been applied to this or a super class
211246
@hasMixin: (mixin) -> _.contains(@_getAppliedMixins(), mixin)
212247

213-
@_applyMixin: (mixin) ->
248+
# ============= PRIVATE STATIC ============== #
249+
250+
@_subClasses: []
214251

252+
@_applyMixin: (mixin) ->
215253
# Add the original mixin to the registry so we can ask if a specific
216254
# mixin has been added to a host class / instance
217-
@_registerMixin mixin
255+
# Each class has its own mixins array
256+
hasMixins = @_appliedMixins?
257+
areInherited = hasMixins and @superClass('_appliedMixins') is @_appliedMixins
258+
if !hasMixins or areInherited then @_appliedMixins = []
259+
260+
# Keep the mixins array clean from duplicates
261+
@_appliedMixins.push(mixin) if !_.contains(@_appliedMixins, mixin)
218262

219263
# Create a clone so that we can remove properties without affecting the global mixin
220264
mixinCopy = _.clone mixin
@@ -224,8 +268,11 @@ class Space.Object
224268
delete mixinCopy.onConstruction
225269

226270
# Mixin static properties into the host class
227-
_.extend(this, mixinCopy.statics) if mixinCopy.statics?
228-
delete mixinCopy.statics
271+
if mixinCopy.statics?
272+
statics = mixinCopy.statics
273+
_.extend(this, statics)
274+
_.extend(sub, statics) for sub in @subClasses()
275+
delete mixinCopy.statics
229276

230277
# Give mixins the chance to do static setup when applied to the host class
231278
mixinCopy.onMixinApplied?.call this
@@ -234,7 +281,11 @@ class Space.Object
234281
# Copy over the mixin to the prototype and merge objects
235282
@_mergeIntoPrototype @prototype, mixinCopy
236283

237-
@_getAppliedMixins: -> @_appliedMixins ? []
284+
@_getAppliedMixins: ->
285+
mixins = []
286+
mixins = mixins.concat(@superClass()._getAppliedMixins()) if @hasSuperClass()
287+
mixins = mixins.concat(@_appliedMixins) if @_appliedMixins?
288+
return _.uniq(mixins)
238289

239290
@_mergeIntoPrototype: (prototype, extension) ->
240291
# Helper function to check for object literals only
@@ -248,22 +299,4 @@ class Space.Object
248299
else
249300
value = _.clone(value) if isPlainObject(value)
250301
# Set non-existing props and override existing methods
251-
prototype[key] = value
252-
253-
@_registerMixin: (mixin) ->
254-
# A bit ugly but necessary to check that sub classes don't statically
255-
# inherit mixin arrays from their super classes
256-
if @__super__?
257-
superMixins = @__super__.constructor._appliedMixins
258-
hasInheritedMixins = @_appliedMixins? and (superMixins is @_appliedMixins)
259-
else
260-
hasInheritedMixins = false
261-
262-
if hasInheritedMixins
263-
# Create a shallow copy of the inherited mixins array
264-
@_appliedMixins = @_appliedMixins.slice()
265-
else
266-
@_appliedMixins ?= []
267-
268-
# Keep the mixins array clean from duplicates
269-
@_appliedMixins.push(mixin) if !_.contains(@_appliedMixins, mixin)
302+
prototype[key] = value

tests/unit/object.unit.coffee

+63-12
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ describe 'Space.Object', ->
5454
MyClass = Space.Object.extend('MyClass')
5555
expect(Space.resolvePath 'MyClass').to.equal(MyClass)
5656

57-
5857
describe "working with static class properties", ->
5958

6059
it 'allows you to define static class properties', ->
@@ -82,6 +81,45 @@ describe 'Space.Object', ->
8281
expect(instance.first).to.equal 1
8382
expect(instance.second).to.equal 2
8483

84+
describe "inheritance helpers", ->
85+
86+
Base = Space.Object.extend {
87+
statics: { prop: 'static', method: -> }
88+
prop: 'prototype'
89+
method: ->
90+
}
91+
Sub = Base.extend()
92+
GrandSub = Sub.extend()
93+
94+
describe "static", ->
95+
96+
it "can tell if there is a super class", ->
97+
expect(Sub.hasSuperClass()).to.be.true
98+
99+
it "can return the super class", ->
100+
expect(Sub.superClass()).to.equal(Base)
101+
102+
it "can return a static prop or method of the super class", ->
103+
expect(Sub.superClass('prop')).to.equal(Base.prop)
104+
expect(Sub.superClass('method')).to.equal(Base.method)
105+
106+
it "can give back a flat array of sub classes", ->
107+
expect(Base.subClasses()).to.eql [Sub, GrandSub]
108+
expect(Sub.subClasses()).to.eql [GrandSub]
109+
expect(GrandSub.subClasses()).to.eql []
110+
111+
describe "prototype", ->
112+
113+
it "can tell if there is a super class", ->
114+
expect(new Sub().hasSuperClass()).to.be.true
115+
116+
it "can return the super class", ->
117+
expect(new Sub().superClass()).to.equal(Base)
118+
119+
it "can return a static prop or method of the super class", ->
120+
expect(new Sub().superClass('prop')).to.equal(Base::prop)
121+
expect(new Sub().superClass('method')).to.equal(Base::method)
122+
85123
describe 'mixins', ->
86124

87125
it 'adds methods to the prototype', ->
@@ -122,17 +160,6 @@ describe 'Space.Object', ->
122160
TestClass.mixin myMixin
123161
expect(myMixin.onMixinApplied).to.have.been.calledOnce
124162

125-
it "does not apply mixins to super classes", ->
126-
firstMixin = onDependenciesReady: sinon.spy()
127-
secondMixin = onDependenciesReady: sinon.spy()
128-
SuperClass = Space.Object.extend()
129-
SuperClass.mixin firstMixin
130-
SubClass = SuperClass.extend()
131-
SubClass.mixin secondMixin
132-
new SuperClass().onDependenciesReady()
133-
expect(firstMixin.onDependenciesReady).to.have.been.calledOnce
134-
expect(secondMixin.onDependenciesReady).not.to.have.been.called
135-
136163
it 'can be defined as prototype property when extending classes', ->
137164
myMixin = { onMixinApplied: sinon.spy() }
138165
MyClass = Space.Object.extend mixin: [myMixin]
@@ -160,6 +187,30 @@ describe 'Space.Object', ->
160187
expect(instance.hasMixin(SecondMixin)).to.be.true
161188
expect(instance.hasMixin(ThirdMixin)).to.be.false
162189

190+
describe "mixin inheritance", ->
191+
192+
it "does not apply mixins to super classes", ->
193+
firstMixin = {}
194+
secondMixin = {}
195+
SuperClass = Space.Object.extend mixin: firstMixin
196+
SubClass = SuperClass.extend mixin: secondMixin
197+
expect(SuperClass.hasMixin(firstMixin)).to.be.true
198+
expect(SuperClass.hasMixin(secondMixin)).to.be.false
199+
expect(SubClass.hasMixin(firstMixin)).to.be.true
200+
expect(SubClass.hasMixin(secondMixin)).to.be.true
201+
202+
it "inherits mixins to children when added to base class later on", ->
203+
LateMixin = { statics: { test: 'property' } }
204+
# Base class with a mixin
205+
BaseClass = Space.Object.extend()
206+
# Sublcass with its own mixin
207+
SubClass = BaseClass.extend()
208+
# Later we extend base class
209+
BaseClass.mixin LateMixin
210+
# Sub class should have all three mixins correctly applied
211+
expect(SubClass.hasMixin(LateMixin)).to.be.true
212+
expect(SubClass.test).to.equal LateMixin.statics.test
213+
163214
describe "onDependenciesReady hooks", ->
164215

165216
it "can provide a hook that is called when dependencies of host class are ready", ->

0 commit comments

Comments
 (0)