Test refactoring

Test refactoring

--- src/manikin-spec.coffee
+++ src/manikin-spec.coffee
@@ -475,6 +475,32 @@
         x[1].name.should.eql 'julia'
         x[1].age.should.eql 27
         x[1].city.should.eql 'sthlm'
+      ).then ->
+        api.close(done)
+
+
+
+    it "can delete an object", (done) ->
+      api = manikin.create()
+
+      model =
+        stuffz:
+          fields:
+            name: 'string'
+            age: 'number'
+            city: 'string'
+
+      saved = {}
+
+      promise(api).connect(connectionData, model, noErr())
+      .post('stuffz', { name: 'jakob', age: 28, city: 'gbg' }, noErr())
+      .post('stuffz', { name: 'julia', age: 27, city: 'gbg' }, noErr())
+      .delOne('stuffz', { name: 'julia' }, noErr())
+      .list('stuffz', { }, noErr (x) ->
+        x.should.have.length 1
+        x[0].name.should.eql 'jakob'
+        x[0].age.should.eql 28
+        x[0].city.should.eql 'gbg'
       ).then ->
         api.close(done)

@@ -713,7 +739,42 @@

-    it "should provide some typical http-operations", (done) ->
+    it "should detect when an filter matches no objects on getOne", (done) ->
+      api = manikin.create()
+
+      model =
+        table:
+          fields:
+            v1: 'string'
+
+      promise(api).connect(connectionData, model, noErr())
+      .getOne('table', { filter: { v1: '123' } }, (err, data) ->
+        err.should.eql new Error()
+        err.toString().should.eql 'Error: No match'
+        should.not.exist data
+      ).then ->
+        api.close(done)
+
+
+    it "should detect when an filter matches no objects on delOne", (done) ->
+      api = manikin.create()
+
+      model =
+        table:
+          fields:
+            v1: 'string'
+
+      promise(api).connect(connectionData, model, noErr())
+      .delOne('table', { v1: '123' }, (err, data) ->
+        err.should.eql new Error()
+        err.toString().should.eql 'Error: No such id'
+        should.not.exist data
+      ).then ->
+        api.close(done)
+
+
+
+    it "should be possible to specifiy the owner together with the fields when creating an object", (done) ->
       api = manikin.create()

       model =
@@ -729,289 +790,16 @@
             name: { type: 'string', default: '' }
             orgnr: { type: 'string', default: '' }

-        employees:
-          owners:
-            company: 'companies'
-          fields:
-            name: { type: 'string', default: '' }
-
-        customers:
-          fields:
-            name: { type: 'string' }
-            at: { type: 'hasMany', model: 'companies' }
-
-      saved = {}
-
-      promise(api).connect(connectionData, model, noErr())
-      .post('accounts', { name: 'n1' }, noErr (a1) ->
-        a1.should.have.keys ['name', 'id']
-        saved.a1 = a1
-      ).then('post', -> @ 'accounts', { name: 'n2' }, noErr (a2) ->
-        a2.should.have.keys ['name', 'id']
-        saved.a2 = a2
-      ).list('accounts', {}, noErr (accs) ->
-        accs.should.eql [saved.a1, saved.a2]
-      ).then('getOne', -> @ 'accounts', { filter: { id: saved.a1.id } }, noErr (acc) ->
-        acc.should.eql saved.a1
-      ).then('getOne', -> @ 'accounts', { filter: { name: 'n2' } }, noErr (acc) ->
-        acc.should.eql saved.a2
-      ).then('getOne', -> @ 'accounts', { filter: { name: 'does-not-exist' } }, (err, acc) ->
-        err.toString().should.eql 'Error: No match'
-        should.not.exist acc
-
-      ).then('post', -> @ 'companies', { account: saved.a1.id, name: 'J Dev AB', orgnr: '556767-2208' }, noErr (company) ->
-        company.should.have.keys ['name', 'orgnr', 'account', 'id', 'at']
-        saved.c1 = company
-      ).then('post', -> @ 'companies', { account: saved.a1.id, name: 'Lean Machine AB', orgnr: '123456-1234' }, noErr (company) ->
-        company.should.have.keys ['name', 'orgnr', 'account', 'id', 'at']
-        saved.c2 = company
-      ).then('post', -> @ 'employees', { company: saved.c1.id, name: 'Jakob' }, noErr (company) ->
-        company.should.have.keys ['name', 'company', 'account', 'id']
-
-      # testing to get an account without nesting
-      ).then('getOne', -> @ 'accounts', { filter: { id: saved.a1.id } }, noErr (acc) ->
-        _(acc).omit('id').should.eql { name: 'n1' }
-
-      # testing to get an account with nesting
-      ).then('getOne', -> @ 'accounts', { nesting: 1, filter: { id: saved.a1.id } }, noErr (acc) ->
-        _(acc).omit('id').should.eql { name: 'n1' }
-
-
-      ).then ->
-        api.close(done)
-
-
-
-
-    it "should delete many-to-many-relations when objects are deleted", (done) ->
-      api = manikin.create()
-
-      model =
-        petsY:
-          fields:
-            name: 'string'
-
-        foodsY:
-          fields:
-            name: 'string'
-            eatenBy: { type: 'hasMany', model: 'petsY', inverseName: 'eats' }
-
-      saved = {}
-
-      promise(api).connect(connectionData, model, noErr())
-      .then('post', -> @('petsY', { name: 'pet1' }, noErr (res) -> saved.pet1 = res))
-      .then('post', -> @('foodsY', { name: 'food1' }, noErr (res) -> saved.food1 = res))
-      .then('postMany', -> @('foodsY', saved.food1.id, 'eatenBy', saved.pet1.id, noErr()))
-      .then('getMany', -> @('petsY', saved.pet1.id, 'eats', noErr((data) -> data.length.should.eql 1)))
-      .then('delOne', -> @('petsY', { id: saved.pet1.id }, noErr()))
-      .then('list', -> @('petsY', { }, noErr((data) -> data.length.should.eql 0)))
-      .then('list', -> @('foodsY', { }, noErr (data) -> data.should.eql [
-        id: saved.food1.id
-        name: 'food1'
-        eatenBy: []
-      ]))
-      .then -> api.close(done)
-
-
-
-    it "should be possible to query many-to-many-relationships", (done) ->
-      api = manikin.create()
-
-      model =
-        people:
-          owners: {}
-          fields:
-            name: { type: 'string', default: '' }
-            boundDevices: { type: 'hasMany', model: 'devices', inverseName: 'boundPeople' }
-
-        devices:
-          owners: {}
-          fields:
-            name: { type: 'string', default: '' }
-
-      saved = {}
-
-      promise(api).connect(connectionData, model, noErr())
-      .post('people', { name: 'q1' }, noErr (q1) ->
-        saved.q1 = q1
-      ).post('people', { name: 'q2' }, noErr (q2) ->
-        saved.q2 = q2
-      ).post('devices', { name: 'd1' }, noErr (d1) ->
-        saved.d1 = d1
-      ).post('devices', { name: 'd2' }, noErr (d2) ->
-        saved.d2 = d2
-      ).then 'postMany', -> @('people',  saved.q1.id, 'boundDevices', saved.d1.id, noErr())
-      .then 'postMany', -> @('people',  saved.q1.id, 'boundDevices', saved.d2.id, noErr())
-      .then 'getMany',  -> @('people',  saved.q1.id, 'boundDevices', noErr((data) -> data.length.should.eql 2))
-      .then 'getMany',  -> @('people',  saved.q1.id, 'boundDevices', { name: 'd1' }, noErr((data) -> data.length.should.eql 1))
-      .then 'getMany',  -> @('people',  saved.q1.id, 'boundDevices', { name: 'd2' }, noErr((data) -> data.length.should.eql 1))
-      .then 'getMany',  -> @('people',  saved.q1.id, 'boundDevices', { name: 'd3' }, noErr((data) -> data.length.should.eql 0))
-      .then 'getMany',  -> @('people',  saved.q2.id, 'boundDevices', noErr((data) -> data.length.should.eql 0))
-      .then 'getMany',  -> @('devices', saved.d1.id, 'boundPeople',  noErr((data) -> data.length.should.eql 1))
-      .then 'getMany',  -> @('devices', saved.d2.id, 'boundPeople',  noErr((data) -> data.length.should.eql 1))
-      .then 'delMany',  -> @('people',  saved.q1.id, 'boundDevices', saved.d1.id, noErr())
-      .then 'getMany',  -> @('people',  saved.q1.id, 'boundDevices', noErr((data) -> data.length.should.eql 1))
-      .then 'getMany',  -> @('people',  saved.q2.id, 'boundDevices', noErr((data) -> data.length.should.eql 0))
-      .then 'getMany',  -> @('devices', saved.d1.id, 'boundPeople',  noErr((data) -> data.length.should.eql 0))
-      .then 'getMany',  -> @('devices', saved.d2.id, 'boundPeople',  noErr((data) -> data.length.should.eql 1))
-
-      .then -> api.close(done)
-
-
-
-
-    it "should delete many-to-many-relations even when owners of the related objects are deleted", (done) ->
-      api = manikin.create()
-
-      model =
-        peopleX:
-          fields:
-            name: 'string'
-
-        petsX:
-          owners: { person: 'peopleX' }
-          fields:
-            name: 'string'
-
-        foodsX:
-          owners: {}
-          fields:
-            name: 'string'
-            eatenBy: { type: 'hasMany', model: 'petsX', inverseName: 'eats' }
-
-      saved = {}
-
-      promise(api).connect(connectionData, model, noErr())
-      .post('peopleX', { name: 'p1' }, noErr (res) ->
-        saved.person = res
-      ).then 'post', -> @('petsX', { person: saved.person.id, name: 'pet1' }, noErr (res) -> saved.pet1 = res)
-      .then 'post', -> @('foodsX', { name: 'food1' }, noErr (res) -> saved.food1 = res)
-      .then 'postMany', -> @('foodsX',  saved.food1.id, 'eatenBy', saved.pet1.id, noErr())
-      .then 'getMany',  -> @('petsX', saved.pet1.id, 'eats', noErr((data) -> data.length.should.eql 1))
-      .then 'delOne',  -> @('peopleX', { id: saved.person.id }, noErr())
-      .then 'list',  -> @('peopleX', { }, noErr((data) -> data.length.should.eql 0))
-      .then 'list',  -> @('petsX', { }, noErr((data) -> data.length.should.eql 0))
-      .then 'list',  -> @('foodsX', { }, noErr (data) -> data.should.eql [
-        id: saved.food1.id
-        name: 'food1'
-        eatenBy: []
-      ])
-      .then -> api.close(done)
-
-
-
-    it "should prevent duplicate many-to-many values, even when data is posted in parallel", (done) ->
-      api = manikin.create()
-
-      model =
-        typeA:
-          fields:
-            name: 'string'
-
-        typeB:
-          fields:
-            name: 'string'
-            belongsTo: { type: 'hasMany', model: 'typeA', inverseName: 'belongsTo2' }
-
-      saved = {}
-      resultStatuses = {}
-
       api.connect connectionData, model, noErr ->
-        api.post 'typeA', { name: 'a1' }, noErr (a1) ->
-          api.post 'typeB', { name: 'b1' }, noErr (b1) ->
-            async.forEach [1,2,3], (item, callback) ->
-              api.postMany 'typeB', b1.id, 'belongsTo', a1.id, noErr (result) ->
-                resultStatuses[result.status] = resultStatuses[result.status] || 0
-                resultStatuses[result.status]++
-                callback()
-            , ->
-              resultStatuses['inserted'].should.eql 1
-              resultStatuses['insert already in progress'].should.eql 2
-              api.list 'typeA', {}, noErr (x) ->
-                x[0].belongsTo2.length.should.eql 1
-                api.close(done)
-
-
-
-    it "should prevent duplicate many-to-many values, even when data is posted in parallel, to both end-points", (done) ->
-      api = manikin.create()
-
-      model =
-        typeC:
-          fields:
-            name: 'string'
-
-        typeD:
-          fields:
-            name: 'string'
-            belongsTo: { type: 'hasMany', model: 'typeC', inverseName: 'belongsTo2' }
-
-      saved = {}
-      resultStatuses = {}
-
-      api.connect connectionData, model, noErr ->
-        api.post 'typeC', { name: 'c1' }, noErr (c1) ->
-          api.post 'typeD', { name: 'd1' }, noErr (d1) ->
-            async.forEach [['typeC', c1.id, 'belongsTo2', d1.id], ['typeD', d1.id, 'belongsTo', c1.id]], (item, callback) ->
-              f = noErr (result) ->
-                resultStatuses[result.status] = resultStatuses[result.status] || 0
-                resultStatuses[result.status]++
-                callback()
-              api.postMany.apply(api, item.concat([f]))
-            , ->
-              resultStatuses['inserted'].should.eql 1
-              resultStatuses['insert already in progress'].should.eql 1
-              api.list 'typeC', {}, noErr (x) ->
-                x[0].belongsTo2.length.should.eql 1
-                api.close(done)
-
-
-
-    it "should raise an error on posts to invalid many to many-properties", (done) ->
-      api = manikin.create()
-
-      model =
-        typeA:
-          fields:
-            name: 'string'
-
-        typeB:
-          fields:
-            name: 'string'
-            belongsTo: { type: 'hasMany', model: 'typeA' }
-
-      saved = {}
-      api.connect connectionData, model, noErr ->
-        api.post 'typeA', { name: 'a' }, noErr (a) ->
-          api.post 'typeB', { name: 'b' }, noErr (b) ->
-            api.postMany 'typeA', a.id, 'relation-that-does-not-exist', b.id, (err) ->
-              err.should.eql new Error()
-              err.toString().should.eql 'Error: Invalid many-to-many property'
-              api.close(done)
-
-
-
-    it "should raise an error on deletes to invalid many to many-properties", (done) ->
-      api = manikin.create()
-
-      model =
-        typeA:
-          fields:
-            name: 'string'
-
-        typeB:
-          fields:
-            name: 'string'
-            belongsTo: { type: 'hasMany', model: 'typeA' }
-
-      saved = {}
-      api.connect connectionData, model, noErr ->
-        api.post 'typeA', { name: 'a' }, noErr (a) ->
-          api.post 'typeB', { name: 'b' }, noErr (b) ->
-            api.delMany 'typeA', a.id, 'relation-that-does-not-exist', b.id, (err) ->
-              err.should.eql new Error()
-              err.toString().should.eql 'Error: Invalid many-to-many property'
-              api.close(done)
+        api.post 'accounts', { name: 'a1' }, noErr (account) ->
+          account.should.have.keys ['name', 'id']
+          api.post 'companies', { name: 'n', orgnr: 'nbr', account: account.id }, noErr (company) ->
+            _(company).omit('id').should.eql {
+              account: account.id
+              name: 'n'
+              orgnr: 'nbr'
+            }
+            api.close(done)

@@ -1040,109 +828,32 @@

-    it "should raise an error if a putOne attempts to put non-existing fields", (done) ->
-      api = manikin.create()
-
-      model =
+    it "must specify an existing owner of the right type when posting", (done) ->
+      api = manikin.create()
+
+      model =
+        stuff:
+          fields: {}
+
         accounts:
-          fields:
-            name: { type: 'string', default: '' }
+          owners: {}
+          fields:
+            name: { type: 'string', default: '' }
+
+        companies:
+          owners:
+            account: 'accounts'
+          fields:
+            name: { type: 'string', default: '' }
+            orgnr: { type: 'string', default: '' }

       api.connect connectionData, model, noErr ->
-        api.post 'accounts', { name: 'a1' }, noErr (account) ->
-          api.putOne 'accounts', { name: 'n1', age: 10, desc: 'test' }, { id: account.id }, (err, data) ->
-            err.should.eql new Error()
-            err.toString().should.eql 'Error: Invalid fields: age, desc'
-            api.close(done)
-
-
-
-    it "should raise an error if a putOne attempts to put using an id from another collection", (done) ->
-      api = manikin.create()
-
-      model =
-        accounts:
-          fields:
-            name: { type: 'string', default: '' }
-        other:
-          fields:
-            name: { type: 'string', default: '' }
-
-      api.connect connectionData, model, noErr ->
-        api.post 'other', { name: 'a1' }, noErr (other) ->
+        api.post 'stuff', { }, noErr (stuff) ->
           api.post 'accounts', { name: 'a1' }, noErr (account) ->
-            api.putOne 'accounts', { name: 'n1' }, { id: other.id }, (err, data) ->
-              err.should.eql new Error()
-              err.toString().should.eql 'Error: No such id'
+            account.should.have.keys ['name', 'id']
+            api.post 'companies', { name: 'n', orgnr: 'nbr', account: stuff.id }, (err, company) ->
+              should.exist err # expect something more precise...
               api.close(done)
-
-
-
-    it "should raise an error if a putOne attempts to put using an id from another collection", (done) ->
-      api = manikin.create()
-
-      model =
-        accounts:
-          fields:
-            name: { type: 'string', default: '' }
-
-      api.connect connectionData, model, noErr ->
-        api.post 'accounts', { name: 'a1' }, noErr (account) ->
-          api.putOne 'accounts', { name: 'n1' }, { id: 1 }, (err, data) ->
-            err.should.eql new Error()
-            err.toString().should.eql 'Error: No such id'
-            api.close(done)
-
-
-
-    it "should allow undefined values", (done) ->
-      api = manikin.create()
-
-      model =
-        pizzas:
-          owners: {}
-          fields:
-            name:
-              type: 'string'
-
-      api.connect connectionData, model, noErr ->
-        api.post 'pizzas', { name: 'jakob' }, noErr (res) ->
-          api.putOne 'pizzas', { name: undefined }, { id: res.id }, noErr (res) ->
-            should.not.exist res.name
-            api.close(done)
-
-
-
-    it "should allow custom validators", (done) ->
-      api = manikin.create()
-
-      model =
-        pizzas:
-          owners: {}
-          fields:
-            name:
-              type: 'string'
-              validate: (apiRef, value, callback) ->
-                api.should.eql apiRef
-                callback(value.length % 2 == 0)
-
-      indata = [
-        name: 'jakob'
-        response: 'something wrong'
-      ,
-        name: 'tobias'
-        response: null
-      ]
-
-      api.connect connectionData, model, noErr ->
-        async.forEach indata, (d, callback) ->
-          api.post 'pizzas', { name: d.name }, (err, res) ->
-            if d.response != null
-              err.message.should.eql 'Validation failed'
-              err.errors.name.path.should.eql 'name'
-            callback()
-        , ->
-          api.close(done)

@@ -1209,14 +920,432 @@
       .list('companies2', {}, noErr ((res) -> res.length.should.eql 2))
       .list('accounts', {}, noErr ((res) -> res.length.should.eql 1))

-      .then('delOne', -> @ 'companies2', { id: saved.company.id }, noErr())
-
-      .list('pets', {}, noErr ((res) -> res.length.should.eql 1))
-      .list('contacts', {}, noErr ((res) -> res.length.should.eql 1))
-      .list('companies2', {}, noErr ((res) -> res.length.should.eql 1))
-      .list('accounts', {}, noErr ((res) -> res.length.should.eql 1))
-
       .then -> api.close(done)
+
+
+
+    it "should delete owned objects when deleting ancestors", (done) ->
+       api = manikin.create()
+
+       model =
+         accounts:
+           owners: {}
+           fields:
+             name: { type: 'string', default: '' }
+
+         companies2:
+           owners:
+             account: 'accounts'
+           fields:
+             name: { type: 'string', default: '' }
+             orgnr: { type: 'string', default: '' }
+
+         contacts:
+           owners:
+             company: 'companies2'
+           fields:
+             email: { type: 'string', default: '' }
+             phone: { type: 'string', default: '' }
+
+         pets:
+           owners:
+             contact: 'contacts'
+           fields:
+             race: { type: 'string', default: '' }
+
+       saved = {}
+
+       promise(api).connect(connectionData, model, noErr())
+       .post('accounts', { name: 'a1', bullshit: 123 }, noErr (account) ->
+         saved.account = account
+       ).then('post', -> @ 'companies2', { name: 'n', orgnr: 'nbr', account: saved.account.id }, noErr (company) ->
+         saved.company = company
+       ).then('post', -> @ 'companies2', { name: 'n2', orgnr: 'nbr', account: saved.account.id }, noErr (company2) ->
+         saved.company2 = company2
+       ).then('post', -> @ 'contacts', { email: '@', phone: '112', company: saved.company.id }, noErr (contact) ->
+         saved.contact = contact
+       ).then('post', -> @ 'contacts', { email: '@2', phone: '911', company: saved.company2.id }, noErr (contact2) ->
+         saved.contact2 = contact2
+       ).then('post', -> @ 'pets', { race: 'dog', contact: saved.contact.id }, noErr (pet) ->
+       ).then('post', -> @ 'pets', { race: 'dog', contact: saved.contact2.id }, noErr (pet) ->
+       ).list('pets', {}, noErr ((res) -> res.length.should.eql 2))
+       .list('contacts', {}, noErr ((res) -> res.length.should.eql 2))
+       .list('companies2', {}, noErr ((res) -> res.length.should.eql 2))
+       .list('accounts', {}, noErr ((res) -> res.length.should.eql 1))
+       .then('delOne', -> @ 'companies2', { id: saved.company.id }, noErr())
+       .list('pets', {}, noErr ((res) -> res.length.should.eql 1))
+       .list('contacts', {}, noErr ((res) -> res.length.should.eql 1))
+       .list('companies2', {}, noErr ((res) -> res.length.should.eql 1))
+       .list('accounts', {}, noErr ((res) -> res.length.should.eql 1))
+       .then -> api.close(done)
+
+
+
+    it "should raise an error if a putOne attempts to put using an id from another collection", (done) ->
+      api = manikin.create()
+
+      model =
+        accounts:
+          fields:
+            name: { type: 'string', default: '' }
+
+      api.connect connectionData, model, noErr ->
+        api.post 'accounts', { name: 'a1' }, noErr (account) ->
+          api.putOne 'accounts', { name: 'n1' }, { id: 1 }, (err, data) ->
+            err.should.eql new Error()
+            err.toString().should.eql 'Error: No such id'
+            api.close(done)
+
+
+
+    it "should allow undefined values", (done) ->
+      api = manikin.create()
+
+      model =
+        pizzas:
+          owners: {}
+          fields:
+            name:
+              type: 'string'
+
+      api.connect connectionData, model, noErr ->
+        api.post 'pizzas', { name: 'jakob' }, noErr (res) ->
+          api.putOne 'pizzas', { name: undefined }, { id: res.id }, noErr (res) ->
+            should.not.exist res.name
+            api.close(done)
+
+
+
+    it "should allow custom validators", (done) ->
+      api = manikin.create()
+
+      model =
+        pizzas:
+          owners: {}
+          fields:
+            name:
+              type: 'string'
+              validate: (apiRef, value, callback) ->
+                api.should.eql apiRef
+                callback(value.length % 2 == 0)
+
+      indata = [
+        name: 'jakob'
+        response: 'something wrong'
+      ,
+        name: 'tobias'
+        response: null
+      ]
+
+      api.connect connectionData, model, noErr ->
+        async.forEach indata, (d, callback) ->
+          api.post 'pizzas', { name: d.name }, (err, res) ->
+            if d.response != null
+              err.message.should.eql 'Validation failed'
+              err.errors.name.path.should.eql 'name'
+            callback()
+        , ->
+          api.close(done)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    it "should delete many-to-many-relations when objects are deleted", (done) ->
+      api = manikin.create()
+
+      model =
+        petsY:
+          fields:
+            name: 'string'
+
+        foodsY:
+          fields:
+            name: 'string'
+            eatenBy: { type: 'hasMany', model: 'petsY', inverseName: 'eats' }
+
+      saved = {}
+
+      promise(api).connect(connectionData, model, noErr())
+      .then('post', -> @('petsY', { name: 'pet1' }, noErr (res) -> saved.pet1 = res))
+      .then('post', -> @('foodsY', { name: 'food1' }, noErr (res) -> saved.food1 = res))
+      .then('postMany', -> @('foodsY', saved.food1.id, 'eatenBy', saved.pet1.id, noErr()))
+      .then('getMany', -> @('petsY', saved.pet1.id, 'eats', noErr((data) -> data.length.should.eql 1)))
+      .then('delOne', -> @('petsY', { id: saved.pet1.id }, noErr()))
+      .then('list', -> @('petsY', { }, noErr((data) -> data.length.should.eql 0)))
+      .then('list', -> @('foodsY', { }, noErr (data) -> data.should.eql [
+        id: saved.food1.id
+        name: 'food1'
+        eatenBy: []
+      ]))
+      .then -> api.close(done)
+
+
+
+    it "should be possible to query many-to-many-relationships", (done) ->
+      api = manikin.create()
+
+      model =
+        people:
+          owners: {}
+          fields:
+            name: { type: 'string', default: '' }
+            boundDevices: { type: 'hasMany', model: 'devices', inverseName: 'boundPeople' }
+
+        devices:
+          owners: {}
+          fields:
+            name: { type: 'string', default: '' }
+
+      saved = {}
+
+      promise(api).connect(connectionData, model, noErr())
+      .post('people', { name: 'q1' }, noErr (q1) ->
+        saved.q1 = q1
+      ).post('people', { name: 'q2' }, noErr (q2) ->
+        saved.q2 = q2
+      ).post('devices', { name: 'd1' }, noErr (d1) ->
+        saved.d1 = d1
+      ).post('devices', { name: 'd2' }, noErr (d2) ->
+        saved.d2 = d2
+      ).then 'postMany', -> @('people',  saved.q1.id, 'boundDevices', saved.d1.id, noErr())
+      .then 'postMany', -> @('people',  saved.q1.id, 'boundDevices', saved.d2.id, noErr())
+      .then 'getMany',  -> @('people',  saved.q1.id, 'boundDevices', noErr((data) -> data.length.should.eql 2))
+      .then 'getMany',  -> @('people',  saved.q1.id, 'boundDevices', { name: 'd1' }, noErr((data) -> data.length.should.eql 1))
+      .then 'getMany',  -> @('people',  saved.q1.id, 'boundDevices', { name: 'd2' }, noErr((data) -> data.length.should.eql 1))
+      .then 'getMany',  -> @('people',  saved.q1.id, 'boundDevices', { name: 'd3' }, noErr((data) -> data.length.should.eql 0))
+      .then 'getMany',  -> @('people',  saved.q2.id, 'boundDevices', noErr((data) -> data.length.should.eql 0))
+      .then 'getMany',  -> @('devices', saved.d1.id, 'boundPeople',  noErr((data) -> data.length.should.eql 1))
+      .then 'getMany',  -> @('devices', saved.d2.id, 'boundPeople',  noErr((data) -> data.length.should.eql 1))
+      .then 'delMany',  -> @('people',  saved.q1.id, 'boundDevices', saved.d1.id, noErr())
+      .then 'getMany',  -> @('people',  saved.q1.id, 'boundDevices', noErr((data) -> data.length.should.eql 1))
+      .then 'getMany',  -> @('people',  saved.q2.id, 'boundDevices', noErr((data) -> data.length.should.eql 0))
+      .then 'getMany',  -> @('devices', saved.d1.id, 'boundPeople',  noErr((data) -> data.length.should.eql 0))
+      .then 'getMany',  -> @('devices', saved.d2.id, 'boundPeople',  noErr((data) -> data.length.should.eql 1))
+
+      .then -> api.close(done)
+
+
+
+
+    it "should delete many-to-many-relations even when owners of the related objects are deleted", (done) ->
+      api = manikin.create()
+
+      model =
+        peopleX:
+          fields:
+            name: 'string'
+
+        petsX:
+          owners: { person: 'peopleX' }
+          fields:
+            name: 'string'
+
+        foodsX:
+          owners: {}
+          fields:
+            name: 'string'
+            eatenBy: { type: 'hasMany', model: 'petsX', inverseName: 'eats' }
+
+      saved = {}
+
+      promise(api).connect(connectionData, model, noErr())
+      .post('peopleX', { name: 'p1' }, noErr (res) ->
+        saved.person = res
+      ).then 'post', -> @('petsX', { person: saved.person.id, name: 'pet1' }, noErr (res) -> saved.pet1 = res)
+      .then 'post', -> @('foodsX', { name: 'food1' }, noErr (res) -> saved.food1 = res)
+      .then 'postMany', -> @('foodsX',  saved.food1.id, 'eatenBy', saved.pet1.id, noErr())
+      .then 'getMany',  -> @('petsX', saved.pet1.id, 'eats', noErr((data) -> data.length.should.eql 1))
+      .then 'delOne',  -> @('peopleX', { id: saved.person.id }, noErr())
+      .then 'list',  -> @('peopleX', { }, noErr((data) -> data.length.should.eql 0))
+      .then 'list',  -> @('petsX', { }, noErr((data) -> data.length.should.eql 0))
+      .then 'list',  -> @('foodsX', { }, noErr (data) -> data.should.eql [
+        id: saved.food1.id
+        name: 'food1'
+        eatenBy: []
+      ])
+      .then -> api.close(done)
+
+
+
+    it "should prevent duplicate many-to-many values, even when data is posted in parallel", (done) ->
+      api = manikin.create()
+
+      model =
+        typeA:
+          fields:
+            name: 'string'
+
+        typeB:
+          fields:
+            name: 'string'
+            belongsTo: { type: 'hasMany', model: 'typeA', inverseName: 'belongsTo2' }
+
+      saved = {}
+      resultStatuses = {}
+
+      api.connect connectionData, model, noErr ->
+        api.post 'typeA', { name: 'a1' }, noErr (a1) ->
+          api.post 'typeB', { name: 'b1' }, noErr (b1) ->
+            async.forEach [1,2,3], (item, callback) ->
+              api.postMany 'typeB', b1.id, 'belongsTo', a1.id, noErr (result) ->
+                resultStatuses[result.status] = resultStatuses[result.status] || 0
+                resultStatuses[result.status]++
+                callback()
+            , ->
+              resultStatuses['inserted'].should.eql 1
+              resultStatuses['insert already in progress'].should.eql 2
+              api.list 'typeA', {}, noErr (x) ->
+                x[0].belongsTo2.length.should.eql 1
+                api.close(done)
+
+
+
+    it "should prevent duplicate many-to-many values, even when data is posted in parallel, to both end-points", (done) ->
+      api = manikin.create()
+
+      model =
+        typeC:
+          fields:
+            name: 'string'
+
+        typeD:
+          fields:
+            name: 'string'
+            belongsTo: { type: 'hasMany', model: 'typeC', inverseName: 'belongsTo2' }
+
+      saved = {}
+      resultStatuses = {}
+
+      api.connect connectionData, model, noErr ->
+        api.post 'typeC', { name: 'c1' }, noErr (c1) ->
+          api.post 'typeD', { name: 'd1' }, noErr (d1) ->
+            async.forEach [['typeC', c1.id, 'belongsTo2', d1.id], ['typeD', d1.id, 'belongsTo', c1.id]], (item, callback) ->
+              f = noErr (result) ->
+                resultStatuses[result.status] = resultStatuses[result.status] || 0
+                resultStatuses[result.status]++
+                callback()
+              api.postMany.apply(api, item.concat([f]))
+            , ->
+              resultStatuses['inserted'].should.eql 1
+              resultStatuses['insert already in progress'].should.eql 1
+              api.list 'typeC', {}, noErr (x) ->
+                x[0].belongsTo2.length.should.eql 1
+                api.close(done)
+
+
+
+    it "should raise an error on posts to invalid many to many-properties", (done) ->
+      api = manikin.create()
+
+      model =
+        typeA:
+          fields:
+            name: 'string'
+
+        typeB:
+          fields:
+            name: 'string'
+            belongsTo: { type: 'hasMany', model: 'typeA' }
+
+      saved = {}
+      api.connect connectionData, model, noErr ->
+        api.post 'typeA', { name: 'a' }, noErr (a) ->
+          api.post 'typeB', { name: 'b' }, noErr (b) ->
+            api.postMany 'typeA', a.id, 'relation-that-does-not-exist', b.id, (err) ->
+              err.should.eql new Error()
+              err.toString().should.eql 'Error: Invalid many-to-many property'
+              api.close(done)
+
+
+
+    it "should raise an error on deletes to invalid many to many-properties", (done) ->
+      api = manikin.create()
+
+      model =
+        typeA:
+          fields:
+            name: 'string'
+
+        typeB:
+          fields:
+            name: 'string'
+            belongsTo: { type: 'hasMany', model: 'typeA' }
+
+      saved = {}
+      api.connect connectionData, model, noErr ->
+        api.post 'typeA', { name: 'a' }, noErr (a) ->
+          api.post 'typeB', { name: 'b' }, noErr (b) ->
+            api.delMany 'typeA', a.id, 'relation-that-does-not-exist', b.id, (err) ->
+              err.should.eql new Error()
+              err.toString().should.eql 'Error: Invalid many-to-many property'
+              api.close(done)
+
+
+
+    it "should raise an error if a putOne attempts to put non-existing fields", (done) ->
+      api = manikin.create()
+
+      model =
+        accounts:
+          fields:
+            name: { type: 'string', default: '' }
+
+      api.connect connectionData, model, noErr ->
+        api.post 'accounts', { name: 'a1' }, noErr (account) ->
+          api.putOne 'accounts', { name: 'n1', age: 10, desc: 'test' }, { id: account.id }, (err, data) ->
+            err.should.eql new Error()
+            err.toString().should.eql 'Error: Invalid fields: age, desc'
+            api.close(done)
+
+
+
+    it "should raise an error if a putOne attempts to put using an id from another collection", (done) ->
+      api = manikin.create()
+
+      model =
+        accounts:
+          fields:
+            name: { type: 'string', default: '' }
+        other:
+          fields:
+            name: { type: 'string', default: '' }
+
+      api.connect connectionData, model, noErr ->
+        api.post 'other', { name: 'a1' }, noErr (other) ->
+          api.post 'accounts', { name: 'a1' }, noErr (account) ->
+            api.putOne 'accounts', { name: 'n1' }, { id: other.id }, (err, data) ->
+              err.should.eql new Error()
+              err.toString().should.eql 'Error: No such id'
+              api.close(done)
+

     it "should provide has-one-relations", (done) ->
@@ -1316,3 +1445,72 @@
       ).then('list', -> @ 'monkeys', {}, noErr (monkeys) ->
         monkeys.length.should.eql 1
       ).then(done)
+
+
+
+    it "should provide some typical http-operations", (done) ->
+      api = manikin.create()
+
+      model =
+        accounts:
+          owners: {}
+          fields:
+            name: { type: 'string', default: '' }
+
+        companies:
+          owners:
+            account: 'accounts'
+          fields:
+            name: { type: 'string', default: '' }
+            orgnr: { type: 'string', default: '' }
+
+        employees:
+          owners:
+            company: 'companies'
+          fields:
+            name: { type: 'string', default: '' }
+
+        customers:
+          fields:
+            name: { type: 'string' }
+            at: { type: 'hasMany', model: 'companies' }
+
+      saved = {}
+
+      promise(api).connect(connectionData, model, noErr())
+      .post('accounts', { name: 'n1' }, noErr (a1) ->
+        a1.should.have.keys ['name', 'id']
+        saved.a1 = a1
+      ).then('post', -> @ 'accounts', { name: 'n2' }, noErr (a2) ->
+        a2.should.have.keys ['name', 'id']
+        saved.a2 = a2
+      ).list('accounts', {}, noErr (accs) ->
+        accs.should.eql [saved.a1, saved.a2]
+      ).then('getOne', -> @ 'accounts', { filter: { id: saved.a1.id } }, noErr (acc) ->
+        acc.should.eql saved.a1
+      ).then('getOne', -> @ 'accounts', { filter: { name: 'n2' } }, noErr (acc) ->
+        acc.should.eql saved.a2
+      ).then('getOne', -> @ 'accounts', { filter: { name: 'does-not-exist' } }, (err, acc) ->
+        err.toString().should.eql 'Error: No match'
+        should.not.exist acc
+
+      ).then('post', -> @ 'companies', { account: saved.a1.id, name: 'J Dev AB', orgnr: '556767-2208' }, noErr (company) ->
+        company.should.have.keys ['name', 'orgnr', 'account', 'id', 'at']
+        saved.c1 = company
+      ).then('post', -> @ 'companies', { account: saved.a1.id, name: 'Lean Machine AB', orgnr: '123456-1234' }, noErr (company) ->
+        company.should.have.keys ['name', 'orgnr', 'account', 'id', 'at']
+        saved.c2 = company
+      ).then('post', -> @ 'employees', { company: saved.c1.id, name: 'Jakob' }, noErr (company) ->
+        company.should.have.keys ['name', 'company', 'account', 'id']
+
+      # testing to get an account without nesting
+      ).then('getOne', -> @ 'accounts', { filter: { id: saved.a1.id } }, noErr (acc) ->
+        _(acc).omit('id').should.eql { name: 'n1' }
+
+      # testing to get an account with nesting
+      ).then('getOne', -> @ 'accounts', { nesting: 1, filter: { id: saved.a1.id } }, noErr (acc) ->
+        _(acc).omit('id').should.eql { name: 'n1' }
+
+
+      ).then ->
+        api.close(done)


Change order that items appear in when sidebar collapses

Correct order is "This Week", "Performance Forecast", and then "Reference Book"

--- src/components/student-dashboard/dashboard.cjsx
+++ src/components/student-dashboard/dashboard.cjsx
@@ -46,37 +46,31 @@

         <BS.Row>
-          <BS.Col xs=12 md=4 lg=3 mdPush=8 lgPush=9>

+          <BS.Col xs=12 md=8 lg=9>
+            <BS.TabbedArea
+              activeKey = {@state.selectedTabIndex}
+              onSelect  = {@selectTab}
+              animation = {false}>
+              <BS.TabPane eventKey={1} tab='This Week'>
+                <ThisWeekPanel courseId={courseId}/>
+                <UpcomingPanel courseId={courseId}/>
+              </BS.TabPane>
+              <BS.TabPane eventKey={2} tab='All Past Work'>
+                <AllEventsByWeek courseId={courseId}/>
+              </BS.TabPane>
+            </BS.TabbedArea>
+          </BS.Col>
+
+          <BS.Col xs=12 md=4 lg=3>
             <ProgressGuideShell courseId={courseId} />
-

               <BrowseBookButton unstyled courseId={courseId}>

Browse the Book

               </BrowseBookButton>
-

           </BS.Col>

-          <BS.Col xs=12 md=8 lg=9 mdPull=4 lgPull=3>
-
-            <BS.TabbedArea
-              activeKey = {@state.selectedTabIndex}
-              onSelect  = {@selectTab}
-              animation = {false}>
-
-              <BS.TabPane eventKey={1} tab='This Week'>
-                <ThisWeekPanel courseId={courseId}/>
-                <UpcomingPanel courseId={courseId}/>
-              </BS.TabPane>
-
-              <BS.TabPane eventKey={2} tab='All Past Work'>
-                <AllEventsByWeek courseId={courseId}/>
-              </BS.TabPane>
-
-            </BS.TabbedArea>
-
-          </BS.Col>
         </BS.Row>


Using elapsed time in delayed events

Using elapsed time in delayed events

--- test/timed_events.coffee
+++ test/timed_events.coffee
@@ -17,7 +17,7 @@
     o.delay n, ->
       ok(true)

-  o.update(0.)
+  o.update(0.5)

 # Clear out the module
 module()


vm: callback parameter to `VirtualizationController#provisionVm` method

--- client/Main/VirtualizationController.coffee
+++ client/Main/VirtualizationController.coffee
@@ -401,7 +401,7 @@

     productForm.on "Cancel", modal.bound "destroy"

-  provisionVm: ({ subscription, paymentMethod, productData })->
+  provisionVm: ({ subscription, paymentMethod, productData }, callback) ->
     { JVM } = KD.remote.api

     { plan, pack } = productData
@@ -413,18 +413,20 @@
       plan.subscribe paymentMethod.paymentMethodId, (err, subscription) =>
         return  if KD.showError err

-        @provisionVm { subscription, productData }
+        @provisionVm { subscription, productData }, callback

       return

     payment.debitSubscription subscription, pack, (err, nonce) =>
-      return  if KD.showError err
+      return callback err  if err

       JVM.createVmByNonce nonce, (err, vm) =>
         return  if KD.showError err

         @emit 'VMListChanged'
         @showVMDetails vm
+
+        callback null, nonce

   askForApprove:(command, callback)->


MainTabs: keeping scroll position implemented

--- client/Main/maintabs/maintabpaneview.coffee
+++ client/Main/maintabs/maintabpaneview.coffee
@@ -6,3 +6,23 @@
     options.type = options.behavior

     super options, data
+
+  show: ->
+
+    super
+
+    KD.utils.defer =>
+      {body, documentElement}   = document
+      documentElement.scrollTop = @lastScrollTops.window
+      body.scrollTop            = @lastScrollTops.body
+
+
+  hide: ->
+
+    return  unless @active
+
+    {body, documentElement} = document
+    @lastScrollTops.window  = documentElement.scrollTop
+    @lastScrollTops.body    = body.scrollTop
+
+    super


Move content into JS

Move content into JS

--- js/outer/home.coffee
+++ js/outer/home.coffee
@@ -5,3 +5,30 @@
     _kmq.push(['trackClickOnOutboundLink', '.kissAuthGithub', 'join link clicked'])
     _kmq.push(['trackClickOnOutboundLink', '#second-join', 'footer join link clicked'])
     _gaq.push(['_trackPageview', '/homepage'])
+
+  viewContext: =>
+    features: [
+      icon: "icon-magic"
+      headline: "Quick setup"
+      teaser: "Set up your CI server in 20 seconds, not two days. We detect test settings for a wide range of web apps, and set them up automatically on our servers."
+    ,
+      icon: "icon-bolt"
+      headline: "Crazy fast tests"
+      teaser: "Your productivity relies on fast test results, and CircleCI runs your tests faster than your Macbook Pro, EC2, your local server, or any other service."
+    ,
+      icon: "icon-bullhorn"
+      headline: "Smart notifications"
+      teaser: "CircleCI intelligently notifies you about just the pushes you care about, not all pushes, and does it over email, Hipchat, Campfire and more."
+    ,
+      icon: "icon-beaker"
+      headline: "Deep customization"
+      teaser: "Real applications often deviate slightly from standard configurations, so CircleCI does too. Our configuration is so flexible that it's easy to tweak almost anything you need."
+    ,
+      icon: "icon-cog"
+      headline: "Debug with ease"
+      teaser: "When your tests are broken, we help you get them fixed. We auto-detect errors, have great support, and even allow you to SSH into our machines to test manually."
+    ,
+      icon: "icon-comments"
+      headline: "Incredible support"
+      teaser: "We respond to support requests immediately, every day. Most requests are responded to within an hour. No-one ever waits more than 12 hours for a response."
+    ]


welcomemodal: clean the unnecessary routes to find correct back route

--- client/home/lib/welcome/welcomemodal.coffee
+++ client/home/lib/welcome/welcomemodal.coffee
@@ -2,6 +2,7 @@
 HomeWelcome = require './'
 LocalStorage = require 'app/localstorage'
 globals = require 'globals'
+_ = require 'lodash'

 module.exports = class WelcomeModal extends kd.ModalView

@@ -82,10 +83,14 @@

     { router } = kd.singletons
     { visitedRoutes: tempVisitedRoutes } = router
-    # To remove infinite loop between these 2 routes
-    # which are `/Welcome` and `/Home` and We are going to
-    # redirect to `Any route` after destroying Welcome Modal
-    # `Any route` -> `/Welcome` -> `/Home` -> `/Welcome`
+    # In case of visitedRoutes comes like this
+    # `Any route` -> `/Welcome` -> `/Home` -> `/Welcome` ->
+    # `/Home` -> `/Welcome` -> `/Home` -> `/Welcome`
+    # to findout correct back route and prevent infinite loop
+    # between `/Welcome` and `/Home` routes, we need to remove
+    # those unnecessary paths to access `/Any Route`
+    # FIXME: ~HK
+    tempVisitedRoutes =  _.flatten _.uniqWith _.chunk(tempVisitedRoutes, 2), _.isEqual
     [..., beforeLast, last] = tempVisitedRoutes

     tempVisitedRoutes.pop()  if last is '/Welcome' and beforeLast.indexOf('/Home') > -1


This works perfectly with https://github.com/tonyhffong/Lint.jl/pull/175

--- lib/linter-julia.coffee
+++ lib/linter-julia.coffee
@@ -77,8 +77,8 @@
           connection.on('error',reject)
           connection.on 'data', (chunk) ->
             data.push(chunk)
-            if chunk.asciiSlice().match("\n\n")
-              connection.destroy()
+            #if chunk.asciiSlice().match("\n\n")
+            #  connection.destroy()
           connection.on 'close', () ->
             allOfData = data.join('')
             resolve(doSomeMagic(allOfData,textEditor))


:bug: Fix Tab Line count

:bug: Fix Tab Line count

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -142,7 +142,7 @@
       @messages.forEach (message) =>
         if message.currentFile and message.range?.intersectsRow @currentLine
           @lineMessages.add message
-      @tabs['Line'].count = @lineMessages.length
+      @tabs['Line'].count = @lineMessages.size
     if shouldRender then @renderPanelMessages()

   # This method is called when we get the status-bar service


Setup split handle event handlers to implement functionality

--- client/ide/lib/views/tabview/ideview.coffee
+++ client/ide/lib/views/tabview/ideview.coffee
@@ -40,6 +40,16 @@
     @on 'PlusHandleClicked', @bound 'createPlusContextMenu'
     @on 'CloseHandleClicked', @bound 'closeSplitView'
     @on 'FullscreenHandleClicked', @bound 'toggleFullscreen'
+    @on 'VerticalSplitHandleClicked', =>
+      {frontApp} = kd.singletons.appManager
+      frontApp.setActiveTabView @tabView
+      frontApp.splitVertically()
+      @ensureSplitHandlers()
+    @on 'HorizontalSplitHandleClicked', =>
+      {frontApp} = kd.singletons.appManager
+      frontApp.setActiveTabView @tabView
+      frontApp.splitHorizontally()
+      @ensureSplitHandlers()

     @tabView.on 'MachineTerminalRequested', @bound 'openMachineTerminal'
     @tabView.on 'MachineWebPageRequested',  @bound 'openMachineWebPage'


Removes obsolete code

Removes obsolete code

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -100,16 +100,10 @@
   updateMinimapView: ->
     # update minimap-editor
     setImmediate =>
-      # FIXME Due to racing conditions during the `destroyed`
-      # event dispatch the editor can be null, until a better
-      # solution is implemented it will prevent the delayed
-      # code from raising an error.
-      if @editor?
-        @transform @miniScrollView[0], @translateY(0)
-        @miniEditorView.setEditorView(@editorView)
+      @miniEditorView.setEditorView(@editorView)

     # offset minimap
-    @offset({ 'top': @editorView.offset().top })
+    @offset top: @editorView.offset().top

     # reset size of minimap layer
     @resetMinimapTransform()
@@ -120,11 +114,9 @@

     # reset minimap-overlayer
     # top will be set 0 when reseting
-    @miniOverlayer.addClass 'hide'
     @miniOverlayer.css
       width: @scrollViewRect.width
       height: @editorViewRect.height
-    @miniOverlayer.removeClass 'hide'

     @transform @miniWrapper[0], @minimapScale


bongo: use redis client

bongo: use redis client

--- node_modules_koding/bongo/lib/expressify.coffee
+++ node_modules_koding/bongo/lib/expressify.coffee
@@ -10,16 +10,8 @@
   { rateLimitOptions } = options

   if rateLimitOptions?.enabled
-    # monitoring redis was already unreachable for ages
-    # TODO(cihangir): replace rate limiting package
-    redisClient = require('redis').createClient(
-      '6379'
-      '127.0.0.1'
-      {}
-    )
-
     { userRules, guestRules } = rateLimitOptions
-    rateLimiter = new RateLimit redisClient, userRules
+    rateLimiter = new RateLimit bongo.redisClient, userRules

   sendMetrics = (start, message) ->


:fire: comment

:fire: comment

--- lib/selection-wrapper.coffee
+++ lib/selection-wrapper.coffee
@@ -139,9 +139,7 @@
       new Range(tailPoint, point)

   saveProperties: ->
-    # {inspect} = require 'util'
     properties = @captureProperties()
-    # console.log 'saving properties original', inspect(properties)
     unless @selection.isEmpty()
       # We select righted in visual-mode, this translation de-effect select-right-effect
       # so that after restoring preserved poperty we can do activate-visual mode without
@@ -152,8 +150,6 @@
         properties.tail = endPoint
       else
         properties.head = endPoint
-
-    # console.log 'saving properties end translated -1 column', inspect(properties)
     @setProperties(properties)

   captureProperties: ->
@@ -174,7 +170,6 @@

   restoreColumnFromProperties: ->
     {head, tail} = @getProperties()
-    # console.log [tail?.toString(), head?.toString()]
     return unless head? and tail?
     return if @selection.isEmpty()

@@ -184,7 +179,6 @@
       [start, end] = [tail, head]
     [start.row, end.row] = @selection.getBufferRowRange()
     @withKeepingGoalColumn =>
-      # console.log 'restoring in restoreColumnFromProperties', new Point(start, end).toString()
       @setBufferRange([start, end], preserveFolds: true)
       @translateSelectionEndAndClip('backward', translate: false)


Construct endpoints based on main domain name

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- config/main.default.coffee
+++ config/main.default.coffee
@@ -95,6 +95,12 @@
   # Disable Sneaker for kloud.
   KONFIG.kloud.noSneaker = true

+  endpoint = "#{options.protocol}//#{options.domains.main}"
+  KONFIG.goKoding.endpoints.ip.public = "#{endpoint}/-/ip"
+  KONFIG.goKoding.endpoints.ipCheck.public = "#{endpoint}/-/ipCheck"
+  KONFIG.goKoding.endpoints.kdLatest.public = "#{endpoint}/a/kd/#{options.environment}/latest-version.txt"
+  KONFIG.goKoding.endpoints.klientLatest.public = "#{endpoint}/a/klient/#{options.environment}/latest-version.txt"
+
   options.requirementCommands = [
     '$KONFIG_PROJECTROOT/scripts/generate-kite-keys.sh'
   ]


turn off "verbose" mode in vagrant

turn off "verbose" mode in vagrant

--- config/main.vagrant.coffee
+++ config/main.vagrant.coffee
@@ -76,7 +76,7 @@
     numberOfWorkers: 1
     watch       : yes
     queueName   : socialQueueName
-    verbose     : yes
+    verbose     : no
   cacheWorker   :
     login       : 'prod-social'
     watch       : yes


Fix tests

Fix tests

--- coffee/subscriptions.controller.spec.coffee
+++ coffee/subscriptions.controller.spec.coffee
@@ -77,6 +77,12 @@
             notify: sinon.stub()
         }
         provide.value "$tgConfirm", mocks.tgConfirm
+
+    _mockTgTranslate = () ->
+        mocks.tgTranslate = {
+            instant: sinon.stub()
+        }
+        provide.value "$translate", mocks.tgTranslate

     _mocks = () ->
         module ($provide) ->
@@ -89,6 +95,7 @@
             _mockTranslate()
             _paymentsService()
             _mockTgConfirm()
+            _mockTgTranslate()

             return null

@@ -103,12 +110,14 @@
     it "load metas", () ->
         subscriptionsCtrl = controller "ContribSubscriptionsController"

-        mocks.translate.instant.withArgs('SUBSCRIPTIONS.TITLE').returns('title')
-        mocks.translate.instant.withArgs('SUBSCRIPTIONS.SECTION_NAME').returns('section')
+        title = 'title'
+        description = 'description'
+
+        mocks.tgTranslate.instant.withArgs('SUBSCRIPTIONS.TITLE').returns(title)
+        mocks.tgTranslate.instant.withArgs('SUBSCRIPTIONS.SECTION_NAME').returns(description)

         subscriptionsCtrl._loadMetas()
-
-        expect(mocks.appMetaService.setAll).have.been.calledWith('title', 'section')
+        expect(mocks.appMetaService.setAll).have.been.calledWith(title, description)

     it "load User Plans", (done) ->
         promise = mocks.contribSubscriptionsService.fetchMyPlans.promise()
@@ -214,10 +223,11 @@

     it "changed Taiga Data", (done) ->
         promise = mocks.contribSubscriptionsService.fetchMyPlans.promise().resolve()
+        message = mocks.tgTranslate.instant.returns('Test')

         subscriptionsCtrl = controller "ContribSubscriptionsController"

         subscriptionsCtrl._onSuccessChangedData().then () ->
-            expect(mocks.tgConfirm.notify).has.been.calledWith('success', 'OK, has cambiado tus datos correctamente', '', 5000)
+            expect(mocks.tgConfirm.notify).has.been.calledWith('success', 'Test', '', 5000)
             expect(mocks.tgLoader.pageLoaded).has.been.called
             done()


Use built-in array methods instead of underscore

--- lib/range-finder.coffee
+++ lib/range-finder.coffee
@@ -1,8 +1,7 @@
-{_, Range} = require 'atom'
+{Range} = require 'atom'

 module.exports =
 class RangeFinder
-
   # Public
   @rangesFor: (editor) ->
     new RangeFinder(editor).ranges()
@@ -13,16 +12,16 @@
   # Public
   ranges: ->
     selectionRanges = @selectionRanges()
-    if _.isEmpty(selectionRanges)
+    if selectionRanges.length is 0
       [@sortableRangeForEntireBuffer()]
     else
-      _.map selectionRanges, (selectionRange) =>
+      selectionRanges.map (selectionRange) =>
         @sortableRangeFrom(selectionRange)

   # Internal
   selectionRanges: ->
-    _.reject @editor.getSelectedBufferRanges(), (range) ->
-      range.isEmpty()
+    @editor.getSelectedBufferRanges().filter (range) ->
+      not range.isEmpty()

   # Internal
   sortableRangeForEntireBuffer: ->


Added the basic Stream Code for PistachioThis

--- lib/streams/pistachio.coffee
+++ lib/streams/pistachio.coffee
@@ -28,6 +28,20 @@
 class PistachioThis extends Transform
   constructor: ->
     super()
+    @_data = ''
+
+  _transform: (chunk, enc, next) ->
+    @_data += chunk
+    next()
+
+  # We wait for flush to be called to implicitly know the end of the incoming,
+  # and then apply the replace.
+  _flush: ->
+    @_data.replace pistachio_matcher, (pist) -> pist.replace /\@/g, 'this.'
+    # Now that we have replaced what we expect, push out our data
+    @push @_data
+    # And then null to end the stream
+    @push null


merging in #1268 ... michaelficarra's colored REPL.

--- repl.coffee
+++ repl.coffee
@@ -7,9 +7,15 @@
 # Require the **coffee-script** module to get access to the compiler.
 CoffeeScript = require './coffee-script'
 readline     = require 'readline'
+{inspect}    = require 'util'
 {Script}     = require 'vm'

 # REPL Setup
+
+# Config
+enableColours = no
+unless process.platform is 'win32'
+  enableColours = not process.env.NODE_DISABLE_COLORS

 # Start by opening up `stdin` and `stdout`.
 stdin = process.openStdin()
@@ -32,7 +38,8 @@
   backlog = ''
   try
     val = CoffeeScript.eval code, bare: on, globals: on, filename: 'repl'
-    process.stdout.write val + '\n' if val isnt undefined
+    unless val is undefined
+      process.stdout.write inspect(val, no, 2, enableColours) + '\n'
   catch err
     error err
   repl.prompt()


fixed REPL to allow streamed input from stdin

--- repl.coffee
+++ repl.coffee
@@ -119,10 +119,17 @@
     on: ->
   stdin.on 'data', (chunk) ->
     pipedInput += chunk
-  stdin.on 'end', ->
-    for line in pipedInput.trim().split "\n"
+    return if -1 is pipedInput.indexOf "\n"
+    lines = pipedInput.split "\n"
+    pipedInput = lines[lines.length - 1]
+    for line in lines[...-1]
       stdout.write "#{line}\n"
       run line
+  stdin.on 'end', ->
+    if trimmedInput = pipedInput.trim()
+      for line in trimmedInput.split "\n"
+        stdout.write "#{line}\n"
+        run line
     stdout.write '\n'
     process.exit 0
 else


multiple video files (close #1)

multiple video files (close #1)

--- lib/video-player-view.coffee
+++ lib/video-player-view.coffee
@@ -66,7 +66,12 @@
       video.on 'ended', () ->
         self.reloadSrc()
     else
-      video.attr 'src', files[0]
+      counter = 0
+      video.attr 'src', files[counter]
+      video.on 'ended', () ->
+        ++counter
+        if (counter < files.length)
+          video.attr 'src', files[counter]

   reloadSrc: ->
     video = atom.workspaceView.find '.video-player video'


Return early when document.body matches selector

--- src/menu-manager.coffee
+++ src/menu-manager.coffee
@@ -34,6 +34,8 @@
   #
   # Returns true to include the selector, false otherwise.
   includeSelector: (selector) ->
+    return true if document.body.webkitMatchesSelector(selector)
+
     unless @testEditor?
       @testEditor = document.createElement('div')
       @testEditor.classList.add('editor')
@@ -41,7 +43,7 @@
       testBody.classList.add(document.body.classList.toString().split(' ')...)
       testBody.appendChild(@testEditor)

-    document.body.webkitMatchesSelector(selector) or @testEditor.webkitMatchesSelector(selector)
+    @testEditor.webkitMatchesSelector(selector)

   # Public: Refreshes the currently visible menu.
   update: ->


Remove debugging tool; use debug() instead

--- src/microservice.coffee
+++ src/microservice.coffee
@@ -27,9 +27,6 @@
 request = require 'request'

 HTTPError = require './httperror'
-
-psw = (str) ->
-  process.stderr.write str + "\n"

 class Microservice


Order model fixed

Order model fixed

--- src/models/order.coffee
+++ src/models/order.coffee
@@ -1,14 +1,20 @@
 module.exports = (sequelize, DataTypes) ->

   sequelize.define 'Order',
-      bid: DataTypes.FLOAT.UNSIGNED
-      ask: DataTypes.FLOAT.UNSIGNED
-      value:
-        type: DataTypes.FLOAT.UNSIGNED
-        allowNull: false
-        validate:
-          notNull: true
-    , validate:
-        isCorrectOrder: ->
-          if @bid? is @ask?
-            throw new Error 'Order must be bid or ask (but not both)'
+
+    bid: DataTypes.FLOAT.UNSIGNED
+    ask: DataTypes.FLOAT.UNSIGNED
+    value:
+      type: DataTypes.FLOAT.UNSIGNED
+      allowNull: false
+      validate:
+        notNull: true
+
+#    validate:
+#      isCorrectOrder: ->
+#        if @bid? is @ask?
+#          throw new Error 'Order must be bid or ask (but not both)'
+#  }, {
+#defaultScope:
+#  order: '`ask` DESC'
+#}


removed mapquest support bis

removed mapquest support bis

--- vendor/assets/gmaps4rails.mapquest.js.coffee
+++ vendor/assets/gmaps4rails.mapquest.js.coffee
@@ -1,145 +0,0 @@
-# #######################################################################################################
-# ##############################################  Map Quest #############################################
-# #######################################################################################################
-# # http://developer.mapquest.com/web/documentation/sdk/javascript/v7.0/api/MQA.Poi.html
-
-# class @Gmaps4RailsMapquest extends Gmaps4Rails
-
-#   constructor: ->
-#     super
-#     #Map settings
-#     @map_options  = {type:  "map"}  #map type (map, sat, hyb)
-#     @markers_conf = {}
-#     @mergeWithDefault "markers_conf"
-#     @mergeWithDefault "map_options"
-
-#   #////////////////////////////////////////////////////
-#   #/////////////// Basic Objects         //////////////
-#   #////////////////////////////////////////////////////
-
-#   createPoint: (lat, lng) ->
-#     return new MQA.Poi({lat: lat, lng: lng})
-
-#   createLatLng: (lat, lng) ->
-#     return {lat: lat, lng: lng}
-
-#   createLatLngBounds: ->
-
-#   createMap: ->
-#     map = new MQA.TileMap(                        #// Constructs an instance of MQA.TileMap
-#       document.getElementById(@map_options.id),   #//the id of the element on the page you want the map to be added into
-#       @map_options.zoom,                          #//intial zoom level of the map
-#       {lat: @map_options.center_latitude, lng: @map_options.center_longitude},
-#       @map_options.type)                          #//map type (map, sat, hyb)
-
-#     MQA.withModule('zoomcontrol3', (->
-#       map.addControl(
-#         new MQA.LargeZoomControl3(),
-#         new MQA.MapCornerPlacement(MQA.MapCorner.TOP_LEFT)
-#       )
-#     ))
-#     return map
-
-#   createMarkerImage: (markerPicture, markerSize, origin, anchor, scaledSize) ->
-
-#   #////////////////////////////////////////////////////
-#   #////////////////////// Markers /////////////////////
-#   #////////////////////////////////////////////////////
-
-#   createMarker: (args)->
-#     marker = new MQA.Poi( {lat: args.Lat, lng: args.Lng} )
-
-#     if args.marker_picture != ""
-#       icon = new MQA.Icon(args.marker_picture, args.marker_height, args.marker_width)
-#       marker.setIcon(icon)
-#       if args.marker_anchor != null
-#         marker.setBias({x: args.marker_anchor[0], y: args.marker_anchor[1]})
-
-#     if args.shadow_picture != ""
-#       icon = new MQA.Icon(args.shadow_picture, args.shadow_height, args.shadow_width)
-#       marker.setShadow(icon)
-
-#       if args.shadow_anchor != null
-#         marker.setShadowOffset({x: args.shadow_anchor[0], y: args.shadow_anchor[1]})
-
-#     @addToMap marker
-#     return marker
-
-
-#   #// clear markers
-#   clearMarkers: ->
-#     for marker in markers
-#       @clearMarker marker
-
-#   #//show and hide markers
-#   showMarkers: ->
-#     for marker in markers
-#       @showMarker marker
-
-#   hideMarkers: ->
-#     for marker in markers
-#       @hideMarker marker
-
-#   clearMarker: (marker) ->
-#     @removeFromMap(marker.serviceObject)
-
-#   showMarker: (marker) ->
-#     #// marker.serviceObject
-
-#   hideMarker: (marker) ->
-#     #// marker.serviceObject
-
-#   extendBoundsWithMarkers: ->
-#     if @markers.length >=2
-#       @boundsObject = new MQA.RectLL(@markers[0].serviceObject.latLng, @markers[1].serviceObject.latLng)
-#       for marker in @markers
-#         @boundsObject.extend marker.serviceObject.latLng
-
-#   #////////////////////////////////////////////////////
-#   #/////////////////// Clusterer //////////////////////
-#   #////////////////////////////////////////////////////
-
-#   createClusterer: (markers_array) ->
-
-#   clearClusterer: ->
-
-#   #//creates clusters
-#   clusterize: ->
-
-#   #////////////////////////////////////////////////////
-#   #/////////////////// INFO WINDOW ////////////////////
-#   #////////////////////////////////////////////////////
-
-#   #// creates infowindows
-#   createInfoWindow: (marker_container) ->
-#     marker_container.serviceObject.setInfoTitleHTML(marker_container.description)
-#     #//TODO: how to disable the mouseover display when using setInfoContentHTML?
-#     #//marker_container.serviceObject.setInfoContentHTML(marker_container.description);
-
-#   #////////////////////////////////////////////////////
-#   #/////////////////// Other methods //////////////////
-#   #////////////////////////////////////////////////////
-
-#   fitBounds: ->
-#     @serviceObject.zoomToRect @boundsObject if @markers.length >=2
-#     @serviceObject.setCenter @markers[0].serviceObject.latLng if @markers.length == 1
-
-#   centerMapOnUser: ->
-#     @serviceObject.setCenter @userLocation
-
-#   addToMap: (object) ->
-#     @serviceObject.addShape object
-
-#   removeFromMap: (object)->
-#     @serviceObject.removeShape object
-
-#   updateBoundsWithPolylines: ()->
-
-#   updateBoundsWithPolygons: ()->
-
-#   updateBoundsWithCircles: ()->
-
-#   extendMapBounds :->
-
-#   adaptMapToBounds: ->
-#     @fitBounds()


Allow tab-switching when tree-view has input focus

--- keymap.cson
+++ keymap.cson
@@ -186,12 +186,12 @@
 	"shift-cmd-c":    "find-and-replace:toggle-case-option"

 "body.platform-darwin .tree-view":
-	"cmd-1":          "unset!"
-	"cmd-2":          "unset!"
-	"cmd-3":          "unset!"
-	"cmd-4":          "unset!"
-	"cmd-5":          "unset!"
-	"cmd-6":          "unset!"
-	"cmd-7":          "unset!"
-	"cmd-8":          "unset!"
-	"cmd-9":          "unset!"
+	"cmd-1":          "pane:show-item-1"
+	"cmd-2":          "pane:show-item-2"
+	"cmd-3":          "pane:show-item-3"
+	"cmd-4":          "pane:show-item-4"
+	"cmd-5":          "pane:show-item-5"
+	"cmd-6":          "pane:show-item-6"
+	"cmd-7":          "pane:show-item-7"
+	"cmd-8":          "pane:show-item-8"
+	"cmd-9":          "pane:show-item-9"


Remove layers more than 3 ticks old

Remove layers more than 3 ticks old

--- src/main/assets/javascripts/simple.coffee
+++ src/main/assets/javascripts/simple.coffee
@@ -1,3 +1,5 @@
+TopLayers = []
+
 configureFeedBehaviour = ->
   $('ul.breadcrumb li.sub-link').click ->
     selected_view = $(this).data "view"
@@ -55,7 +57,7 @@
     time_diff = time_diff_in_hours + " hours ago"
     if time_diff_in_hours == 0
       time_diff = time_diff_in_minutes + " minutes ago"
-  L.geoJson(v.geo.features[0], {
+  layer = L.geoJson(v.geo.features[0], {
     pointToLayer: (feature, latlon) ->
       markerIcon = L.icon
         iconUrl: 'images/marker-icon.png'
@@ -65,7 +67,13 @@
       return {color: feature.properties.color};
     onEachFeature: (feature, layer) ->
       layer.bindPopup feature.properties.description + "
(#{time_diff})"
-  }).addTo(map)
+  })
+  TopLayers.unshift(layer)
+  layer.addTo(map)
+  if TopLayers.length > 3
+    l = TopLayers.pop()
+    console.log l
+    map.removeLayer(l);

   addFeedItem(time_diff, map, v)


Now model files loads automatically

Now model files loads automatically

--- src/models/index.coffee
+++ src/models/index.coffee
@@ -1,3 +1,5 @@
+fs = require 'fs'
+path = require 'path'
 Sequelize = require 'sequelize'
 config = require process.cwd() + '/app/config'

@@ -11,13 +13,15 @@
   dialect: 'mariadb'
   logging: console.log

-
-# load models
-# TODO: Need loads models automaticaly
-models = ['User'
-          'Order']
-models.forEach (model) ->
-  module.exports[model] = sequelize.import __dirname + '/' + model.toLowerCase()
+# load models automatically from dir
+fs.readdirSync(__dirname)
+  .filter (file) ->
+    if file.search(/\.map$/) is -1 and file isnt 'index.js'
+        file
+  .forEach (file) ->
+    model = file.replace /^./, (match) -> match.toUpperCase()
+    model = model.replace /\.\w+$/, '' # trim file suffix
+    module.exports[model] = sequelize.import path.join __dirname, file

 # describe relationships
 ((m) ->


Fixed slackAttachment for incomplete punch

--- src/models/punch.coffee
+++ src/models/punch.coffee
@@ -374,14 +374,13 @@
     elapsed =
      punchDate = ''
     if @times.block?
-      elapsed = "#{@times.block}"
+      elapsed = "#{@times.block} hours on "
     else if @elapsed?
-      elapsed = "#{@elapsed}"
+      elapsed = "#{@elapsed} hours on "
     if @mode is 'vacation' or
        @mode is 'sick' or
        @mode is 'unpaid'
-      elapsed += " #{@mode}"
-    elapsed += " hours"
+      elapsed += " #{@mode} hours on "

     headers = HEADERS.rawdata
     if @row and @row[headers.today]
@@ -418,7 +417,7 @@
         fields.push outField

     attachment =
-      title: elapsed + " on " + punchDate
+      title: elapsed + punchDate
       text: notes
       fields: fields
       color: color


Updated sally area tag info

Updated sally area tag info

--- views/components/ship-parts/statuslabel.cjsx
+++ views/components/ship-parts/statuslabel.cjsx
@@ -6,19 +6,19 @@
 texts = [
   ['Retreated'],
   ['Repairing'],
-  ['Ship tag: %s', 'E1, E2, E3'],
-  ['Ship tag: %s', 'E4'],
-  ['Ship tag: %s', '?'],
-  ['Ship tag: %s', '?'],
+  ['Ship tag: %s', 'E1, E4'],
+  ['Ship tag: %s', 'E2, E3'],
+  ['Ship tag: %s', 'E5'],
+  ['Ship tag: %s', 'E6'],
   ['Resupply needed']
 ]

 styles = [
   'danger',
   'info',
+  'primary',
   'success',
   'warning',
-  'primary',
   'info',
   'warning'
 ]


OK 2 delete parent cases & add nodes while record

1) Its ok to delete parent cases. A new parent case with exp # will be
created.

2) Its ok to add nodes to the diagram while recording data to CODAP.

[#135187379]
[#137216687]

https://www.pivotaltracker.com/story/show/135187379
https://www.pivotaltracker.com/story/show/137216687

Extract _createDataContext() method..  (WIP might remove this)

(WIP) Try to recover from deleted parent cases.

This approach is pretty buggy.  Instead we will try to use `create items` methods.

WIP: Start using `create item` instead of create case.

WIP: Safety

WIP:  Using create Item, matching dataContext attributes.

WIP: Cleanup & functioning.  Just a bit more cleanup to go.

--- src/code/models/codap-connect.coffee
+++ src/code/models/codap-connect.coffee
@@ -6,16 +6,6 @@

 module.exports = class CodapConnect

-  name: 'SageModeler'
-
-  codapPhone: null
-
-  currentCaseID: null
-
-  standaloneMode: false
-
-  queue: []
-
   @instances: {} # map of context -> instance

   @instance: (context) ->
@@ -25,14 +15,13 @@
   constructor: (context) ->
     log.info 'CodapConnect: initializing'
     GraphStore = require '../stores/graph-store'
+    @standaloneMode = false
+    @queue = []
     @graphStore = GraphStore.store
     @lastTimeSent = @_timeStamp()
     @sendThrottleMs = 300

-    # Create a new case for each experiment:
-    SimulationStore.actions.createExperiment.listen   @_openNewCase.bind(@)
-
-    SimulationStore.actions.recordingFramesCreated.listen  @_addData.bind(@)
+    SimulationStore.actions.recordingFramesCreated.listen  @addData.bind(@)

     CodapActions.sendUndoToCODAP.listen @_sendUndoToCODAP.bind(@)
     CodapActions.sendRedoToCODAP.listen @_sendRedoToCODAP.bind(@)
@@ -66,7 +55,7 @@
         # We check for game state in either the frame (CODAP API 2.0) or the dataContext
         # (API 1.0). We ignore the dataContext if we find game state in the interactiveFrame
         state = frame?.values.savedState or
-                context?.values.contextStorage.gameState
+                context?.values.contextStorage?.gameState

         if state?
           @graphStore.deleteAll()
@@ -75,13 +64,6 @@
         log.info "null response in codap-connect codapPhone.call"
     )

-    timeUnit = TimeUnits.toString SimulationStore.store.stepUnits(), true
-    sampleDataAttrs = [
-      {
-        name: timeUnit
-        type: "numeric"
-      }
-    ]

     # check if we already have a datacontext (if we're opening a saved model).
     # if we don't create one with our collections. Then kick off init
@@ -92,69 +74,45 @@
       if ret?.success
         @initGameHandler ret
       else
-        @codapPhone.call
-          action: 'create',
-          resource: 'dataContext',
-          values: {
-            name: 'Sage Simulation',
-            title: 'Sage Simulation'
-            collections: [ {
-              name: 'Simulation',
-              title: 'Sage Simulation',
-              labels: {
-                singleCase: 'run',
+        @_createDataContext()
+
+
+  _createDataContext: ->
+    sampleDataAttrs = @_getSampleAttributes()
+    message =
+        action: 'create'
+        resource: 'dataContext'
+        values:
+          name: 'Sage Simulation'
+          title: 'Sage Simulation'
+          collections:[
+            {
+              name: 'Simulation'
+              title: 'Sage Simulation'
+              labels:
+                singleCase: 'run'
                 pluralCase: 'runs'
-              },
               attrs: [
-                  {
-                    name: tr '~CODAP.SIMULATION.EXPERIMENT'
-                    type: 'numeric'
-                  }
-                ]
-              },
-              {
-                parent: "Simulation",
-                name: 'Samples',
-                title: 'Samples',
-                labels: {
-                  singleCase: 'sample',
-                  pluralCase: 'samples'
-                }
-                attrs: sampleDataAttrs
-              }
-            ]
-          }
-        , @initGameHandler
-
-  _openNewCase: ->
-    @caseOpened = true
-    caseData = {}
-    caseData[tr '~CODAP.SIMULATION.EXPERIMENT'] = SimulationStore.store.settings.experimentNumber
-    @currentCaseID = null
-    collectionAction = @_createCollection()
-
-    @codapPhone.call([
-      collectionAction,
-      {
-        action: 'create'
-        resource: 'dataContext[Sage Simulation].collection[Simulation].case',
-        values: { values: caseData }
-      }
-    ]
-    , (result) =>
-      if result?
-        [collectionResponse, caseResponse] = result
-
-        if caseResponse.success
-          @currentCaseID = caseResponse.values[0].id
-          @_flushQueue()
-          if not @standaloneMode
-            @createTable()
-        else
-          log.info "CODAP returned an error on 'openCase'"
-    )
-
-  _createCollection: ->
+                name: tr '~CODAP.SIMULATION.EXPERIMENT'
+                type: 'numeric'
+              ]
+            },
+            {
+              parent: "Simulation"
+              name: 'Samples'
+              title: 'Samples'
+              labels:
+                singleCase: 'sample'
+                pluralCase: 'samples'
+              attrs:  sampleDataAttrs
+            }
+          ]
+
+    @codapPhone.call(message, @initGameHandler)
+
+
+  # Return the column headings and types for our samples. (steps, NodeA, nodeB, nodeC)
+  _getSampleAttributes: ->
     nodes = @graphStore.getNodes()

     # First column definition is the time index
@@ -166,62 +124,92 @@
       }
     ]

-    addSampleDataAttr = (node) ->
+    addNodeAttr = (node) ->
       type = if node.valueDefinedSemiQuantitatively then 'qualitative' else 'numeric'
       sampleDataAttrs.push
         name: node.title
         type: type

     _.each nodes, (node) ->
-      addSampleDataAttr(node)
-
-    return {
-      action: 'create',
-      resource: 'dataContext[Sage Simulation].collection[Samples].attribute',
-      values: sampleDataAttrs
-    }
+      addNodeAttr(node)
+
+    return sampleDataAttrs
+
+
+  # If CODAPs Samples collection doesn't have all our data attributes add the new ones.
+  _createMissingDataAttributes: (callback) ->
+    # TODO: Computing this every time is expensive. Use a flag set from GraphChange event?
+    currentAttributes = _.sortBy(@_getSampleAttributes(), 'name')
+    attributesKey = _.pluck(currentAttributes,'name').join("|")
+    if @attributesKey == attributesKey
+      callback()
+    else
+      doResolve = (listAttributeResponse) =>
+        if listAttributeResponse.success
+          values = listAttributeResponse.values
+          newAttributes = _.select currentAttributes, (a) =>  (! _.includes(values,a.name))
+          message =
+            action: 'create'
+            resource: 'dataContext[Sage Simulation].collection[Samples].attribute'
+            values: newAttributes
+          @codapPhone.call message, (response) =>
+            if response.success
+              @attributesKey = attributesKey
+              callback()
+            else
+              log.info "Unable to update Attributes"
+        else
+          log.info "unable to list attributes"
+
+      getListing =
+        action: 'get'
+        resource: 'dataContext[Sage Simulation].collection[Samples].attributeList'
+      @codapPhone.call(getListing, doResolve)
+      log.info "requested list of attributes"
+

   _timeStamp: ->
     new Date().getTime()

+
   _shouldSend: ->
-    @_openNewCase() unless @caseOpened?
-    return false unless @currentCaseID
     currentTime = @_timeStamp()
     elapsedTime = currentTime - @lastTimeSent
     return elapsedTime > @sendThrottleMs

-  _addData: (data) ->
-    @queue = @queue.concat data
-    if @_shouldSend()
-      @_sendSimulationData()
-    else
-      setTimeout(
-        => @_sendSimulationData(),
-        @sendThrottleMs
-      )

   _sendSimulationData: ->
     timeUnit = TimeUnits.toString SimulationStore.store.stepUnits(), true

+    # drain the queue synchronously. Re-add pending data in case of error.
+    pendingData = @queue
+    @queue = []
+
     # Create the sample data values (node values array)
-    sampleData = _.map @queue, (frame) =>
-      sample = {
-        parent: @currentCaseID
-        values: {}
-      }
-      sample.values[timeUnit] = frame.time
-      _.each frame.nodes, (n) -> sample.values[n.title] = n.value
+    sampleData = _.map pendingData, (frame) =>
+      sample = {}
+      sample[tr '~CODAP.SIMULATION.EXPERIMENT'] = SimulationStore.store.settings.experimentNumber
+      sample[timeUnit] = frame.time
+      _.each frame.nodes, (n) -> sample[n.title] = n.value
       sample

-    # Send the data, if any
+    createItemsMessage =
+      action: 'create',
+      resource: "dataContext[Sage Simulation].item",
+      values: sampleData
+
     if sampleData.length > 0
-      @codapPhone.call
-        action: 'create',
-        resource: "dataContext[Sage Simulation].collection[Samples].case",
-        values: sampleData
-    @lastTimeSent = @_timeStamp()
-    @queue = []
+      @createTable()
+      @_createMissingDataAttributes =>
+        # Send the data, if any
+        createItemsCallback = (newSampleResult) =>
+          if newSampleResult.success
+            @lastTimeSent = @_timeStamp()
+          else
+            log.info "CODAP returned an error on 'create item''"
+            # Re-add pending data in case of error.
+            @queue = pendingData.concat @queue
+        @codapPhone.call(createItemsMessage, createItemsCallback)

   _sendUndoToCODAP: ->
@@ -239,11 +227,6 @@
       values: {
         operation: 'redoAction'
       }
-
-  _flushQueue: ->
-    for data in @queue
-      @_sendSimulationData data
-    @queue = []

   sendUndoableActionPerformed: (logMessage) ->
     @codapPhone.call
@@ -254,8 +237,15 @@
         logMessage: logMessage
       }

+  addData: (data) ->
+    @queue = @queue.concat data
+    if @_shouldSend()
+      @_sendSimulationData()
+    else
+      setTimeout(@_sendSimulationData, @sendThrottleMs)
+
   createGraph: (yAttributeName)->
-    @_createCollection()
+    @_createMissingDataAttributes()
     timeUnit = TimeUnits.toString SimulationStore.store.stepUnits(), true

     @codapPhone.call
@@ -268,12 +258,14 @@
         size: { width: 242, height: 221 },
         position: 'bottom'

-  createTable: (yAttributeName)->
-    @codapPhone.call
-      action: 'create',
-      resource: 'component',
-      values:
-        type: 'caseTable'
+  createTable: () ->
+    unless @tableCreated
+      @codapPhone.call
+        action: 'create',
+        resource: 'component',
+        values:
+          type: 'caseTable'
+      @tableCreated = true

   codapRequestHandler: (cmd, callback) =>
     resource = cmd.resource


removed unused require

removed unused require

--- src/coffee/sphere_transformer.coffee
+++ src/coffee/sphere_transformer.coffee
@@ -1,5 +1,4 @@
 Q = require 'q'
-csv = require 'csv'

 _ = require('underscore')._
 _s = require 'underscore.string'


i dunno, some stuff i found

i dunno, some stuff i found

--- lib/decoration-example-view.coffee
+++ lib/decoration-example-view.coffee
@@ -23,21 +23,28 @@
   initialize: (serializeState) ->
     @decorationsByEditorId = {}
     @toggleButtons =
-      line: @lineToggle
-      gutter: @gutterToggle
+      #line: @lineToggle
+      #gutter: @gutterToggle
       highlight: @highlightToggle

-    @lineToggle.on 'click', => @toggleDecorationForCurrentSelection('line')
-    @gutterToggle.on 'click', => @toggleDecorationForCurrentSelection('gutter')
-    @highlightToggle.on 'click', => @toggleDecorationForCurrentSelection('highlight')
+    #@lineToggle.on 'click', => @toggleDecorationForCurrentSelection('line')
+    #@gutterToggle.on 'click', => @toggleDecorationForCurrentSelection('gutter')
+    @highlightToggle.on 'click', => @createDecorationForCurrentSelection('highlight')
+    @removeHighlight.on 'click', => @destroyDecorationsAtCursor()

-    @lineColorCycle.on 'click', => @cycleDecorationColor('line')
-    @gutterColorCycle.on 'click', => @cycleDecorationColor('gutter')
-    @highlightColorCycle.on 'click', => @cycleDecorationColor('highlight')
+    #@lineColorCycle.on 'click', => @cycleDecorationColor('line')
+    #@gutterColorCycle.on 'click', => @cycleDecorationColor('gutter')
+    #@highlightColorCycle.on 'click', => @cycleDecorationColor('highlight')

     atom.workspaceView.on 'pane-container:active-pane-item-changed', => @updateToggleButtonStates()

   ## Decoration API methods
+
+  destroyDecorationsAtCursor: ->
+    markersArr = @getMarkersAtCursor()
+    for marker in markersArr
+      # destroy marker if the marker is a relevant market (i.e. highlighted by user)
+      @destroyMarker(marker) if marker.getProperties()['addy-highlight']

   createDecorationFromCurrentSelection: (editor, type) ->
     # Get the user's selection from the editor
@@ -45,6 +52,7 @@

     # create a marker that never invalidates that folows the user's selection range
     marker = editor.markBufferRange(range, invalidate: 'never')
+    marker.bufferMarker.setProperties('addy-highlight': true)

     # create a decoration that follows the marker. A Decoration object is returned which can be updated
     decoration = editor.decorateMarker(marker, type: type, class: "#{type}-#{@getRandomColor()}")
@@ -54,13 +62,23 @@
     # This allows you to change the class on the decoration
     decoration.update(newDecorationParams)

-  destroyDecorationMarker: (decoration) ->
-    # Destory the decoration's marker because we will no longer need it.
+  destroyMarker: (marker) ->
+    # Destroy the decoration's marker because we will no longer need it.
     # This will destroy the decoration as well. Destroying the marker is the
     # recommended way to destory the decorations.
-    decoration.getMarker().destroy()
+    marker.destroy()
+
+  getMarkersAtCursor: () ->
+    editor = @getEditor()
+    buffer = editor.buffer
+    point = editor.getCursor().getBufferPosition()
+    markers = buffer.findMarkers(containsPoint: point)
+    JSON.stringify(markers)

   ## Button handling methods
+
+  createDecorationForCurrentSelection: ->
+    @createDecorationFromCurrentSelection(@getEditor(), 'highlight')

   toggleDecorationForCurrentSelection: (type) ->
     return unless editor = @getEditor()
@@ -126,3 +144,19 @@
   # Tear down any state and detach
   destroy: ->
     @detach()
+
+  get_all_markers: ->
+    editor = atom.workspace.getActiveEditor()
+    buffer = editor.buffer
+    markers = buffer.getMarkers()
+
+  getAddyMarkers: ->
+    markers = atom.workspace.getActiveEditor()
+    addy_markers = editor.buffer.getMarkers().filter (x) ->
+      x.getProperties()['addy-highlight']
+
+
+
+### !@#$%>haightlighter infos
+highlight all my favorite words ['cheese', 'bread']
+###


nginx: add gatekeeper location

nginx: add gatekeeper location

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -286,6 +286,14 @@
         #{if environment is "sandbox" then basicAuth else ""}
       }

+      location ~ /api/gatekeeper/(.*) {
+        proxy_pass            http://gatekeeper/$1;
+        proxy_set_header      X-Real-IP       $remote_addr;
+        proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_next_upstream   error timeout   invalid_header http_500;
+        proxy_connect_timeout 1;
+      }
+
       #{createLocations(KONFIG)}

       #{createUserMachineLocation("userproxy")}


Update tss.cson

Update tss.cson

Looks better with the other options inside the list

--- grammars/tss.cson
+++ grammars/tss.cson
@@ -1,6 +1,6 @@
 {
   "scopeName": "source.css.tss",
-  "name": "ALLOY.TSS",
+  "name": "Alloy (TSS)",
   "fileTypes": [
     "tss"
   ],


Updated html5-writer (Removed prettification support)

--- src/html5-writer.coffee
+++ src/html5-writer.coffee
@@ -16,6 +16,53 @@
       returnNode = @writeNode node
       returnScope += returnNode
     return returnScope
+
+  writeNode: (node, indentLevel = 0)->
+
+    if node instanceof Html5Node
+
+      signature = tag = '' + node.tag
+
+      for attribute, value of node.attributeMap
+        signature += ' ' + attribute
+        if value
+          signature += '="' + value + '"'
+
+      if node.isNonClosing
+        html = '<' + signature + '>'
+        if node.childrenList.length > 0
+          for child in node.childrenList
+            html += @writeNode child, (indentLevel + 0)
+        return html
+
+      if node.isSelfClosing
+        html = '<' + signature + ' />'
+      else
+        html = '<' + signature + '>'
+
+        if node.childrenList.length > 0
+          for child in node.childrenList
+            html += @writeNode child, (indentLevel + 1)
+        else
+          if node.innerText
+            html += node.innerText
+
+        html += '</' + tag + '>'
+
+      return html
+
+    else if node instanceof Html5TextNode
+
+      return node.innerText
+
+    else if node instanceof Html5CommentNode
+
+      return '<!-- ' + node.innerText + ' -->'
+
+    else
+
+      throw new Error 'Unsupported Node'
+

   writeNodePretty: (node, indentLevel = 0)->


Fix dedup in the scanner

Fix dedup in the scanner

--- library/nucleus/watch.coffee
+++ library/nucleus/watch.coffee
@@ -174,6 +174,7 @@
         augments = augments.map (a) -> a.service
         services = _.filter exports, isService
         services = _.merge services, augments
+        _.unique services

     # Watch the specified directory for addition and changing of
     # the files, looking for modules with services there and then


Debounce calls of codemirror changed

Debounce calls of codemirror changed

--- packages/rocketchat-ui-admin/admin/admin.coffee
+++ packages/rocketchat-ui-admin/admin/admin.coffee
@@ -137,12 +137,21 @@
 	setEditorOnBlur: (_id) ->
 		Meteor.defer ->
 			codeMirror = $('.code-mirror-box[data-editor-id="'+_id+'"] .CodeMirror')[0].CodeMirror
-			codeMirror.on 'change', ->
+			if codeMirror.changeAdded is true
+				return
+
+			onChange = ->
 				value = codeMirror.getValue()
 				TempSettings.update {_id: _id},
 					$set:
 						value: value
 						changed: Settings.findOne(_id).value isnt value
+
+			onChangeDelayed = _.debounce onChange, 500
+
+			codeMirror.on 'change', onChangeDelayed
+			codeMirror.changeAdded = true
+
 		return

 	assetAccept: (fileConstraints) ->


remove token handler from parser

remove token handler from parser

--- src/parser/index.coffee
+++ src/parser/index.coffee
@@ -3,7 +3,6 @@

 require './string'
 workout = require '../workout'
-tokenActions = require './tokens'
 handlers = require './handlers'

 #NUM_INTERVALS X DISTANCE TYPE @ TIME


fix broken jsonp

fix broken jsonp

--- src/chorus.core.coffee
+++ src/chorus.core.coffee
@@ -48,7 +48,7 @@
     timeout ?= 10000
     timer = null
     head = document.getElementsByTagName("HEAD")[0]
-    escape = window.escapeURIComponent
+    escape = window.encodeURIComponent
     query = (for key, value of data
         "#{escape key}=#{escape value}&").join('')
     key = "jsonp_callback_#{jsonp.uid}"


Moved `on "render"` event code to `onRender` function

--- core/app/backbone/views/channel_view.coffee
+++ core/app/backbone/views/channel_view.coffee
@@ -11,24 +11,20 @@
     'click .js-channel': 'showChannelFacts'
     'click .js-topic': 'showTopicFacts'

-  initialize: (opts) ->
-    @on 'render', =>
-      @renderSubChannels()
-      if @model.get('followable?')
-        @addToChannelRegion.show new AddChannelToChannelsButtonView(model: @model)
-      @creatorProfileRegion.show new UserWithAuthorityBox
-        model: @model.user(),
-        authority: @model.get('created_by_authority')
+  onRender: ->
+    @showChannelFacts()

-  renderSubChannels: ->
     if @model.get('inspectable?')
       @subChannelsRegion.show new SubchannelsView
         collection: @model.subchannels()
         model: @model

-  onRender: ->
-    @showChannelFacts()
+    if @model.get('followable?')
+      @addToChannelRegion.show new AddChannelToChannelsButtonView(model: @model)

+    @creatorProfileRegion.show new UserWithAuthorityBox
+      model: @model.user(),
+      authority: @model.get('created_by_authority')

   showChannelFacts: -> @showFacts @channelFacts()
   showTopicFacts: -> @showFacts @topicFacts()
@@ -39,5 +35,4 @@
       collection: facts

   channelFacts: -> new ChannelFacts([], channel: @model)
-
   topicFacts: -> @model.topic().facts()


Fixed missing space

Fixed missing space

--- core/app/backbone/views/channel_view.coffee
+++ core/app/backbone/views/channel_view.coffee
@@ -17,7 +17,7 @@
     @on 'render', =>
       @renderSubChannels()
       @$('header .authority').tooltip
-        title: 'Authority of ' + @model.attributes.created_by.username + 'on "' + @model.attributes.title + '"'
+        title: 'Authority of ' + @model.attributes.created_by.username + ' on "' + @model.attributes.title + '"'

       if @model.get('followable?')
         @addToChannelRegion.show new AddChannelToChannelsButtonView(model: @model)


Fixed problem with parallel plugin information retrieval in cli

--- src/cli/plugins.coffee
+++ src/cli/plugins.coffee
@@ -291,7 +291,7 @@
 					, options.fetch
 				else
 					names = scaffolding.plugins.info.getActive()
-					async.each names, (n, next) ->
+					async.eachSeries names, (n, next) ->
 						getInfo n, (title, text) ->
 							console.log title
 							console.log text


fixing urlToken misnaming

fixing urlToken misnaming

--- src/lib/agent.js.coffee
+++ src/lib/agent.js.coffee
@@ -79,7 +79,7 @@
     FirehoseJS.client.post( params ).done (data) =>
       this._populateWithJSON data
       FirehoseJS.client.APIAccessToken  = @accessToken
-      FirehoseJS.client.URLToken        = @URLToken
+      FirehoseJS.client.URLToken        = @urlToken
       FirehoseJS.Agent.loggedInAgent = this


Readded base indentation code.

Readded base indentation code.

--- src/preprocessor.coffee
+++ src/preprocessor.coffee
@@ -17,6 +17,8 @@
   TERM   = '\uEFFF'

   constructor: ->
+    # `base` is either `null` or a regexp that matches the base indentation
+    @base = null
     # `indents` is an array of successive indentation characters.
     @indents = []
     @context = []
@@ -74,6 +76,12 @@

             # we might require more input to determine indentation
             return if not isEnd and (@ss.check /// [#{ws}\n]* $ ///)?
+
+            if @base?
+              unless (@scan @base)?
+                throw new Error "inconsistent base indentation"
+            else
+              @base = /// #{@scan /// [#{ws}]* ///} ///

             i = 0
             lines = @ss.str.substr(0, @ss.pos).split(/\n/) || ['']


Minor changes

Minor changes

--- lib-src/index.coffee
+++ lib-src/index.coffee
@@ -5,10 +5,10 @@

 module.exports = stdlib =
   # http://stackoverflow.com/a/2548133/1034080
-  endsWith: (string, suffix) ->
-    string.indexOf(suffix, string.length - suffix.length) != -1
+  endsWith: (string, other) ->
+    string.indexOf(other, string.length - other.length) != -1

-  # Just monkey-patch everything
+  # Monkey-patch all methods
   monkey: -> method.monkey?() for own n, method of stdlib

 monkey String, 'endsWith'


delete console.log

delete console.log

--- lib/add-tests.coffee
+++ lib/add-tests.coffee
@@ -37,22 +37,14 @@
     path = resource.relativeUri
     params = {}
     query = {}
-    # console.log('callback addtests' +'dir resoure :/n')
-    # console.log(JSON.stringify(resource, null, 2))
-    # console.log(JSON.stringify(resource.resources[0].methods, null, 2))

     # Apply parent properties
     if parent
       path = parent.path + path
       params = _.clone parent.params
-      # queryparent = _.clone parent.query
-      # console.log('parent query is :')
-      # console.log(JSON.stringify(queryparent, null, 2))
-

     # Setup param
     if resource.uriParameters
-      # console.dir('your uriParameters' + resource.uriParameters)
       for key, param of resource.uriParameters
         params[key] = param.example

@@ -63,18 +55,11 @@
     # Iterate response method
     async.each resource.methods, (api, callback) ->
       method = api.method.toUpperCase()
+
       # Setup query
       if api.queryParameters
-
-        # console.log('your methods.queryParameters is ' + api.queryParameters )
-        # console.log(JSON.stringify(api.queryParameters, null, 2))
-
         for qkey, qvalue of api.queryParameters
           query[qkey] = qvalue.example
-          # console.log('qkey is :'  + 'qvalue is : qvalue.example' )
-        # console.log('the queryParameters is:' + query )
-        # console.log(JSON.stringify(query, null, 2))
-

       # Iterate response status
@@ -100,12 +85,7 @@
           catch
             console.warn "cannot parse JSON example request body for #{test.name}"
         test.request.params = params
-        # console.log('this test queryParameters is:' + query )
-        # console.log(JSON.stringify(query, null, 2))
-        # console.log(test.request)
         test.request.query = query
-        # console.log('this test queryParameters is:' + query )
-        # console.log(JSON.stringify(query, null, 2))

         # Update test.response
         test.response.status = status