brainstem-js 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +8 -0
- data/.pairs +21 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.tm_properties +1 -0
- data/.travis.yml +12 -0
- data/Assetfile +79 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +50 -0
- data/LICENSE.txt +22 -0
- data/README.md +143 -0
- data/Rakefile +25 -0
- data/brainstemjs.gemspec +24 -0
- data/lib/brainstem/js/engine.rb +5 -0
- data/lib/brainstem/js/version.rb +5 -0
- data/lib/brainstem/js.rb +10 -0
- data/spec/brainstem-collection-spec.coffee +141 -0
- data/spec/brainstem-model-spec.coffee +283 -0
- data/spec/brainstem-sync-spec.coffee +22 -0
- data/spec/brainstem-utils-spec.coffee +12 -0
- data/spec/brianstem-expectation-spec.coffee +209 -0
- data/spec/helpers/builders.coffee +80 -0
- data/spec/helpers/jquery-matchers.js +137 -0
- data/spec/helpers/models/post.coffee +14 -0
- data/spec/helpers/models/project.coffee +13 -0
- data/spec/helpers/models/task.coffee +14 -0
- data/spec/helpers/models/time-entry.coffee +13 -0
- data/spec/helpers/models/user.coffee +8 -0
- data/spec/helpers/spec-helper.coffee +79 -0
- data/spec/storage-manager-spec.coffee +613 -0
- data/spec/support/.DS_Store +0 -0
- data/spec/support/console-runner.js +103 -0
- data/spec/support/headless.coffee +47 -0
- data/spec/support/headless.html +60 -0
- data/spec/support/runner.html +85 -0
- data/spec/vendor/backbone-factory.js +62 -0
- data/spec/vendor/backbone.js +1571 -0
- data/spec/vendor/inflection.js +448 -0
- data/spec/vendor/jquery-1.7.js +9300 -0
- data/spec/vendor/jquery.cookie.js +47 -0
- data/spec/vendor/minispade.js +67 -0
- data/spec/vendor/sinon-1.3.4.js +3561 -0
- data/spec/vendor/underscore.js +1221 -0
- data/vendor/assets/.DS_Store +0 -0
- data/vendor/assets/javascripts/.DS_Store +0 -0
- data/vendor/assets/javascripts/brainstem/brainstem-collection.coffee +53 -0
- data/vendor/assets/javascripts/brainstem/brainstem-expectation.coffee +65 -0
- data/vendor/assets/javascripts/brainstem/brainstem-inflections.js +449 -0
- data/vendor/assets/javascripts/brainstem/brainstem-model.coffee +141 -0
- data/vendor/assets/javascripts/brainstem/brainstem-sync.coffee +76 -0
- data/vendor/assets/javascripts/brainstem/index.js +1 -0
- data/vendor/assets/javascripts/brainstem/iso8601.js +41 -0
- data/vendor/assets/javascripts/brainstem/loading-mixin.coffee +13 -0
- data/vendor/assets/javascripts/brainstem/storage-manager.coffee +275 -0
- data/vendor/assets/javascripts/brainstem/utils.coffee +35 -0
- metadata +198 -0
data/.gitignore
ADDED
data/.pairs
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
pairs:
|
2
|
+
rn: Roger Neel
|
3
|
+
ac: Andrew Cantino; andrew
|
4
|
+
jm: Jeff Moore
|
5
|
+
cad: Chris Alvarado-Dryden
|
6
|
+
ml: Matthew LaRocca
|
7
|
+
sa: Sufyan Adam
|
8
|
+
bb: Bobby Brown
|
9
|
+
al: Andrew Langdon
|
10
|
+
kd: Katlyn Daniluk
|
11
|
+
jc: Juan-Carlos Medina
|
12
|
+
rg: Reid Gillette
|
13
|
+
aj: Alec Jacobs
|
14
|
+
mc: Michael Chen
|
15
|
+
sc: Sean Crafts
|
16
|
+
cm: Caleb Mingle
|
17
|
+
aa: André Arko
|
18
|
+
email:
|
19
|
+
prefix: pair
|
20
|
+
domain: mavenlink.com
|
21
|
+
global: true
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
brainstem-js
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-1.9.3-p392
|
data/.tm_properties
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
exclude = {$exclude,bin,build,dist,tmp,.tm_properties}
|
data/.travis.yml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm: 1.9.3
|
3
|
+
|
4
|
+
before_install: gem install bundler
|
5
|
+
|
6
|
+
notifications:
|
7
|
+
hipchat: 09a2719b3284ea0d0d247355fa5542@Mavenlink Dev
|
8
|
+
|
9
|
+
git:
|
10
|
+
depth: 1
|
11
|
+
|
12
|
+
source_key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBOHBpRjdldDZKM2FmWCt0WDlWRG5HZEt4dDdZZVJVVUh3K29JQVJDekorejBvS3MxCnNwaEUxZlBSQksyUDFMUVV5cngyUThzalgxdnA1NUt6U3lnVWExMFRlUG90VnVWN3hZbVFqTG5FM2FueTJBNXIKMFhXZG4wS2k2cUt5TkVwd2ZJa3JYam8xSmY4SHJ0ajZ5Yi9VTFcySnZxbTBGTTZRNmU1TCtmUGdQN0JqeS9WSApUWHF4UWVRaTVaSVppa2JoeGxoK3RQNVZaM1JMRTF3bEhRdXpESUFZemRFdnFXeEFxYkhvNktyMjJSZzRhaGNPCnNEZ1FGK0drR0h2bjBKTkpmc05Cd3d2YUQ2Y3M5N2pnY045b1VrK1phOFNlbFZhazRjWVhRUk5WaTdPMVdkQnkKSE1xaVNPVjFZdEhSWWdwSFpPaHdYSUdxK3ZhUXVFR21SRzdBY1FJREFRQUJBb0lCQVFDOXBiQ2xjdjFXbG13MwpEd0wrK3RUL0llL2VmeGVnN1RzSjFBMlh6NWRPc2ZYM0dJRHM4ZzUxOTVuQi8zQytSbDB1dEMvOEJYVE1ta3o3CnhIbzNXY2pFdWNsOFBJOXZMQTBiT3RSdXZ0Y0F0bGZxd1ROV1ZvejNNSit0cjZ5Q0ptTlRaK1FvVUhhMkVtM08KS1QrOHNpTEx0S01IRXlGOVZwS0EzZEkxUDRwaUhodlFBdkgzQkdRR3piNW9UczV6RXJnRmZNbEZKWFc0S2NxcApYc1NnWDBjd3d3Nit3WDFLblRGY3lNak9QbTB2MWZSSTdHMGdtOG02VVJrakdVeXhpRXhkTVNEdm5yVEt4ajlvCitIK1NucU14RW5uczJRR1lnSEtnV1ZaSFFBUU9UenBNSVdvNndHODNMMDlyOUo3S1dzZHRwNStKeDNZWWE2eTMKU0tod3NQdGxBb0dCQVAvZTJ2SitCVmpkRXVOSjJibWo4YnlGK2lJd292Vm9FYW4xUExkZ3JJbTNQWkFrLytyMgpiSFV0d0xDVjBSR1l5Z1ZqaW42YmEzUitNM2ptanErM3Z1N0FsVlJNazdvcFVXcjUwanRzbDhhT1k4WkIxTlhuClRlRW5keEswTnBWaWs1VlFhNzBqcmh3NGtmekxhUk9xdUV1SGt6Qk95MXhJeStyUkNMNDMzT1ZmQW9HQkFQSzMKOHNXYm5YRzR0WjdHMU5QVjZ0YXJ5bEt4bFluRnhseG4rUmZueTFHbG1NejU4YW1Jb1F2SnJuSHlSR29xQ2dNOQo4eWJFMmVoVjJFM20wKzUwampDeFNrMjhxbkNybDdnbTdNcjhsVzdGQnZLSUw2NDFsM1pDT3pLSnBKWFdGN1BaCmdYVGplTmduRzliQkljMldBNHpzaGVDK05CRWxidmFkekRrT0hkd3ZBb0dBWkRlZ1lCdzE4ZkZkQllNV2NSeWkKZ1Nta3FDR09vam9wdVB6aDFCMWNWdkJiZjRyT1pmUXcxTkNmeVVwVXdlU3JNK01pQ3FiTE5xeDdjcDR6UXVYZwpOZGxlWTg4K2lVckhwZlBGZ1JydWM0bXYwS1pXTzVYR0xpcnIrM3AwYXB4YW04QU5BdDdud2d2eU9pWmR1S05FClhlanpJSmVzRlRBNkZuWGJTODNMaWxjQ2dZRUExai9kd3VUOGM3ZnlTZmVGUW9DZnpXTFRNMitpYW56ei9mbWgKZmFLVWJMdmFSNFdSOW02dWlmTTdVMFhoY2owdG5YTC93WWNlT3VJY0Q1ZmtGNmMzSkhBN0FLZTdZNzEwTFkvZQprY2VvT0tFZTR0T29Fd1VuYjdKREF2ZFJHeHBpemRUL1d5aTRNVVZFWTZzVHBaLzMvbHVDU2NKYnY0N2xoamdBClg1VEFjdTBDZ1lCRFhnNUF2VHVXTE5pRk91RW5RekhrRmp0N0lwRVVUN2lpZDByMHBJempMVmc1c08zSXRqTkQKNjlvWitIYUlCSVpMWkNQc2lpS2dmQkFIS0xXY3NEQTRnelY2ckpUWDdmdXYvcHZUTk9tdmJlcG5OK0JGd05xdApVU2c5UmxFZ0lFWEE3Q041aVFvb2l2SmFiT3VEREl0NUhiTS9Gd1M0V3QvQVdnQUZkYmpab3c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
|
data/Assetfile
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'rake-pipeline-web-filters'
|
2
|
+
require 'jasmine-core'
|
3
|
+
|
4
|
+
output "build"
|
5
|
+
|
6
|
+
input "vendor/assets/javascripts/brainstem" do
|
7
|
+
match "*.coffee" do
|
8
|
+
coffee_script
|
9
|
+
end
|
10
|
+
|
11
|
+
match "*.js" do
|
12
|
+
minispade :rewrite_requires => true, :module_id_generator => proc { |input|
|
13
|
+
input.path.sub(/\.js$/, '')
|
14
|
+
}, :string => true
|
15
|
+
concat "brainstem.js"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
input "spec" do
|
20
|
+
match "**/*.coffee" do
|
21
|
+
coffee_script
|
22
|
+
end
|
23
|
+
|
24
|
+
match "**/*.js" do
|
25
|
+
reject "support/**/*.js"
|
26
|
+
reject "vendor/**/*.js"
|
27
|
+
reject "helpers/**/*.js"
|
28
|
+
minispade :rewrite_requires => true, :module_id_generator => proc { |input|
|
29
|
+
input.path.sub(/\.js$/, '')
|
30
|
+
}, :string => true
|
31
|
+
concat "specs.js"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
input "spec/support" do
|
36
|
+
match "runner.html" do
|
37
|
+
concat "index.html"
|
38
|
+
end
|
39
|
+
|
40
|
+
match "headless.html" do
|
41
|
+
concat
|
42
|
+
end
|
43
|
+
|
44
|
+
match "**/*.coffee" do
|
45
|
+
coffee_script
|
46
|
+
end
|
47
|
+
|
48
|
+
match "**/*.js" do
|
49
|
+
concat
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
input "spec/vendor" do
|
54
|
+
output "build/vendor"
|
55
|
+
match "*.js" do
|
56
|
+
concat
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
input "spec/helpers" do
|
61
|
+
|
62
|
+
match "**/*.coffee" do
|
63
|
+
coffee_script
|
64
|
+
end
|
65
|
+
|
66
|
+
match "*.js" do
|
67
|
+
minispade :rewrite_requires => true, :module_id_generator => proc { |input|
|
68
|
+
input.path.sub(/\.js$/, '')
|
69
|
+
}, :string => true
|
70
|
+
concat "helpers.js"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
input Jasmine::Core.path do
|
75
|
+
output "build/jasmine"
|
76
|
+
match /^[^\/]+\.(js|css)$/ do
|
77
|
+
concat
|
78
|
+
end
|
79
|
+
end
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
GIT
|
2
|
+
remote: git://github.com/livingsocial/rake-pipeline.git
|
3
|
+
revision: 27718232227eddd029466b2968f0880c10a18fb4
|
4
|
+
branch: master
|
5
|
+
specs:
|
6
|
+
rake-pipeline (0.8.0)
|
7
|
+
json
|
8
|
+
rake (~> 10.0.0)
|
9
|
+
thor
|
10
|
+
|
11
|
+
GIT
|
12
|
+
remote: git://github.com/wycats/rake-pipeline-web-filters.git
|
13
|
+
revision: d83f54cc0d4e53f9833c31851964e3d8ba00f66a
|
14
|
+
branch: master
|
15
|
+
specs:
|
16
|
+
rake-pipeline-web-filters (0.6.0)
|
17
|
+
rack
|
18
|
+
rake-pipeline (~> 0.6)
|
19
|
+
|
20
|
+
PATH
|
21
|
+
remote: .
|
22
|
+
specs:
|
23
|
+
brainstem-js (0.2.1)
|
24
|
+
|
25
|
+
GEM
|
26
|
+
remote: https://rubygems.org/
|
27
|
+
specs:
|
28
|
+
coffee-script (2.2.0)
|
29
|
+
coffee-script-source
|
30
|
+
execjs
|
31
|
+
coffee-script-source (1.4.0)
|
32
|
+
execjs (1.4.0)
|
33
|
+
multi_json (~> 1.0)
|
34
|
+
jasmine-core (1.3.1)
|
35
|
+
json (1.7.6)
|
36
|
+
multi_json (1.5.0)
|
37
|
+
rack (1.5.0)
|
38
|
+
rake (10.0.3)
|
39
|
+
thor (0.16.0)
|
40
|
+
|
41
|
+
PLATFORMS
|
42
|
+
ruby
|
43
|
+
|
44
|
+
DEPENDENCIES
|
45
|
+
brainstem-js!
|
46
|
+
bundler (~> 1.2)
|
47
|
+
coffee-script (~> 2.2)
|
48
|
+
jasmine-core (~> 1.3.1)
|
49
|
+
rake-pipeline!
|
50
|
+
rake-pipeline-web-filters!
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Mavenlink
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# Brainstem.js
|
2
|
+
|
3
|
+
[Brainstem](https://github.com/mavenlink/brainstem) is designed to power rich APIs in Rails. The Brainstem gem provides a presenter library that handles converting ActiveRecord objects into structured JSON and a set of API abstractions that allow users to request sorts, filters, and association loads, allowing for simpler implementations, fewer requests, and smaller responses.
|
4
|
+
|
5
|
+
The Brainstem.js library is a companion library for Backbone.js that makes integration with Brainstem APIs a breeze. Brainstem.js adds an identity map and relational models to Backbone.
|
6
|
+
|
7
|
+
## Why Brainstem.js?
|
8
|
+
|
9
|
+
* Speaks natively to Brainstem APIs
|
10
|
+
* Adds relational models in Backbone, allowing you to setup has_one, has_many, and belongs_to relationships.
|
11
|
+
* Provides an Identity Map to avoid loading already-available records.
|
12
|
+
* Supports Brainstem side-loading of multiple objects for fast, single-request workflows.
|
13
|
+
* Interprets the Brainstem results array and hashes for you, abstracting away the JSON protocol.
|
14
|
+
* Written in CoffeeScript.
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
If you're in Rails, just add `brainstem-js` to your Gemfile and require `brainstem` in `application.js`.
|
19
|
+
|
20
|
+
We have a [comprehensive demo available](https://github.com/mavenlink/brainstem-demo-rails). We recommend that you start there.
|
21
|
+
|
22
|
+
What follows is an overview.
|
23
|
+
|
24
|
+
### StorageManager
|
25
|
+
|
26
|
+
The `Brianstem.StorageManager` is in charge of loading data over the API, as well as returning already cached data. We recommend setting one up in a singleton App class.
|
27
|
+
|
28
|
+
class Application
|
29
|
+
constructor: ->
|
30
|
+
@data = new Brainstem.StorageManager()
|
31
|
+
@data.addCollection 'widgets', Collections.Widgets
|
32
|
+
@data.addCollection 'locations', Collections.Locations
|
33
|
+
@data.addCollection 'features', Collections.Features
|
34
|
+
@homeRouter = new Routers.WidgetsRouter()
|
35
|
+
|
36
|
+
$ ->
|
37
|
+
window.base = new Application()
|
38
|
+
Backbone.history.start(root: "/")
|
39
|
+
|
40
|
+
At the moment, the global StorageManager *must* be available at `window.base.data`. For this reason, we recommend making a singleton instance of an `Application` class for holding your `StorageManager` and any other shared resources.
|
41
|
+
|
42
|
+
### Brainstem.Models and Brainstem.Collections
|
43
|
+
|
44
|
+
Once you have a StorageManager, you should setup some `Brainstem.Models` and `Brainstem.Collections`:
|
45
|
+
|
46
|
+
window.Models ?= {}
|
47
|
+
window.Collections ?= {}
|
48
|
+
|
49
|
+
class Models.Widget extends Brainstem.Model
|
50
|
+
paramRoot: 'widget'
|
51
|
+
brainstemKey: 'widgets'
|
52
|
+
urlRoot: '/api/v1/widgets'
|
53
|
+
|
54
|
+
@associations:
|
55
|
+
features: ["features"]
|
56
|
+
location: "locations"
|
57
|
+
|
58
|
+
class Collections.Widgets extends Brainstem.Collection
|
59
|
+
model: Models.Widget
|
60
|
+
url: '/api/v1/widgets'
|
61
|
+
|
62
|
+
class Models.Feature extends Brainstem.Model
|
63
|
+
paramRoot: 'feature'
|
64
|
+
brainstemKey: 'features'
|
65
|
+
urlRoot: '/api/v1/features'
|
66
|
+
|
67
|
+
@associations:
|
68
|
+
widgets: ["widgets"]
|
69
|
+
|
70
|
+
class Collections.Features extends Brainstem.Collection
|
71
|
+
model: Models.Feature
|
72
|
+
url: '/api/v1/features'
|
73
|
+
|
74
|
+
Use the `@associations` class method to declare the mapping between association names and `StorageManager` collections where the data can be located. Arrays indicate has_many relationships. Other than a few additions, these are just Backbone Models and Collections.
|
75
|
+
|
76
|
+
### Backbone.Views
|
77
|
+
|
78
|
+
Now that you have models, collections, and a `StorageManager`, it's time to load some data (probably in Backbone.Views). For example:
|
79
|
+
|
80
|
+
class Views.Widgets.IndexView extends Backbone.View
|
81
|
+
template: JST["backbone/templates/widgets/index"]
|
82
|
+
|
83
|
+
initialize: ->
|
84
|
+
@collection = base.data.loadCollection "widgets", include: ["location", "features"], order: 'updated_at:desc'
|
85
|
+
@collection.bind 'reset', @addAll
|
86
|
+
@collection.bind 'remove', @addAll
|
87
|
+
|
88
|
+
render: =>
|
89
|
+
@$el.html @template()
|
90
|
+
|
91
|
+
if @collection.loaded
|
92
|
+
@addAll()
|
93
|
+
else
|
94
|
+
@$("#widgets-list").text "Just a moment..."
|
95
|
+
|
96
|
+
return this
|
97
|
+
|
98
|
+
addAll: =>
|
99
|
+
@$("#widgets-list").empty()
|
100
|
+
@collection.each(@addOne)
|
101
|
+
@addLocations()
|
102
|
+
|
103
|
+
addOne: (model) =>
|
104
|
+
view = new Views.Widgets.WidgetView(model: model)
|
105
|
+
@$("#widgets-list").append view.render().el
|
106
|
+
|
107
|
+
And finally, in your templates, you can access the relational data just like you'd normally access model data in Backbone.
|
108
|
+
|
109
|
+
@model.get('location').get('name')
|
110
|
+
|
111
|
+
for feature in @model.get('features').models:
|
112
|
+
|
113
|
+
Etc.
|
114
|
+
|
115
|
+
## Development
|
116
|
+
|
117
|
+
We're always open to pull requests!
|
118
|
+
|
119
|
+
### Dependencies
|
120
|
+
|
121
|
+
- PhantomJS. If you're on OS X, run `brew install phantomjs`.
|
122
|
+
|
123
|
+
### Running Specs
|
124
|
+
|
125
|
+
To run the specs on the command line, run:
|
126
|
+
|
127
|
+
bundle exec rake
|
128
|
+
|
129
|
+
To run the specs in a server with live code reloading and compilation:
|
130
|
+
|
131
|
+
bundle exec rake server
|
132
|
+
|
133
|
+
To develop your application against a local checkout of brainstem-js, we suggest using Bundler's local gems:
|
134
|
+
|
135
|
+
bundle config local.brainstem-js ~/workspace/brainstem-js
|
136
|
+
|
137
|
+
And when you're done, run:
|
138
|
+
|
139
|
+
bundle config --delete local.brainstem-js
|
140
|
+
|
141
|
+
# License
|
142
|
+
|
143
|
+
Brainstem and Brainstem.js were created by Mavenlink, Inc. and are available under the MIT License.
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
task :clean do
|
5
|
+
sh "rakep clean"
|
6
|
+
end
|
7
|
+
|
8
|
+
task :build do
|
9
|
+
sh "rakep build"
|
10
|
+
end
|
11
|
+
|
12
|
+
task :spec => :build do
|
13
|
+
begin
|
14
|
+
exec *%w(phantomjs build/headless.js build/headless.html)
|
15
|
+
rescue Errno::ENOENT => e
|
16
|
+
puts "Couldn't find phantomjs."
|
17
|
+
abort "You didn't run `brew install phantomjs`, did you?"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
task :server => :clean do
|
22
|
+
exec "rakep server"
|
23
|
+
end
|
24
|
+
|
25
|
+
task :default => :spec
|
data/brainstemjs.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'brainstem/js/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "brainstem-js"
|
8
|
+
gem.version = Brainstem::Js::VERSION
|
9
|
+
gem.authors = ["Mavenlink"]
|
10
|
+
gem.email = ["opensource@mavenlink.com"]
|
11
|
+
gem.description = %q{The Brainstem API adapter library for Backbone.js}
|
12
|
+
gem.summary = %q{Easily connect Backbone.js with Brainstem APIs. Get relational models in Backbone.}
|
13
|
+
gem.homepage = "http://github.com/mavenlink/brainstem-js"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_development_dependency 'bundler', '~> 1.2'
|
21
|
+
gem.add_development_dependency 'coffee-script', '~> 2.2'
|
22
|
+
gem.add_development_dependency 'jasmine-core', '~> 1.3.1'
|
23
|
+
gem.add_development_dependency 'rake-pipeline-web-filters', '~> 0.6.0'
|
24
|
+
end
|
data/lib/brainstem/js.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
describe 'Brainstem.Collection', ->
|
2
|
+
collection = updateArray = null
|
3
|
+
|
4
|
+
beforeEach ->
|
5
|
+
collection = new Brainstem.Collection([{id: 2, title: "1"}, {id: 3, title: "2"}, {title: "3"}])
|
6
|
+
updateArray = [{id: 2, title: "1 new"}, {id: 4, title: "this is new"}]
|
7
|
+
|
8
|
+
describe 'update', ->
|
9
|
+
it "works with an array", ->
|
10
|
+
collection.update updateArray
|
11
|
+
expect(collection.get(2).get('title')).toEqual "1 new"
|
12
|
+
expect(collection.get(3).get('title')).toEqual "2"
|
13
|
+
expect(collection.get(4).get('title')).toEqual "this is new"
|
14
|
+
|
15
|
+
it "works with a collection", ->
|
16
|
+
newCollection = new Brainstem.Collection(updateArray)
|
17
|
+
collection.update newCollection
|
18
|
+
expect(collection.get(2).get('title')).toEqual "1 new"
|
19
|
+
expect(collection.get(3).get('title')).toEqual "2"
|
20
|
+
expect(collection.get(4).get('title')).toEqual "this is new"
|
21
|
+
|
22
|
+
it "should update copies of the model that are already in the collection", ->
|
23
|
+
model = collection.get(2)
|
24
|
+
spy = jasmine.createSpy()
|
25
|
+
model.bind "change:title", spy
|
26
|
+
collection.update updateArray
|
27
|
+
expect(model.get('title')).toEqual "1 new"
|
28
|
+
expect(spy).toHaveBeenCalled()
|
29
|
+
|
30
|
+
describe "loadNextPage", ->
|
31
|
+
it "loads the next page of data for a collection that has previously been loaded in the storage manager, returns the collection and whether it thinks there is another page or not", ->
|
32
|
+
respondWith server, "/api/time_entries?per_page=2&page=1", resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry(), buildTimeEntry()] }
|
33
|
+
respondWith server, "/api/time_entries?per_page=2&page=2", resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry(), buildTimeEntry()] }
|
34
|
+
respondWith server, "/api/time_entries?per_page=2&page=3", resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry()] }
|
35
|
+
collection = base.data.loadCollection "time_entries", perPage: 2
|
36
|
+
expect(collection.length).toEqual 0
|
37
|
+
server.respond()
|
38
|
+
expect(collection.length).toEqual 2
|
39
|
+
expect(collection.lastFetchOptions.page).toEqual 1
|
40
|
+
|
41
|
+
spy = jasmine.createSpy()
|
42
|
+
collection.loadNextPage success: spy
|
43
|
+
server.respond()
|
44
|
+
expect(spy).toHaveBeenCalledWith(collection, true)
|
45
|
+
expect(collection.lastFetchOptions.page).toEqual 2
|
46
|
+
expect(collection.length).toEqual 4
|
47
|
+
|
48
|
+
spy = jasmine.createSpy()
|
49
|
+
collection.loadNextPage success: spy
|
50
|
+
expect(collection.length).toEqual 4
|
51
|
+
server.respond()
|
52
|
+
expect(spy).toHaveBeenCalledWith(collection, false)
|
53
|
+
expect(collection.lastFetchOptions.page).toEqual 3
|
54
|
+
expect(collection.length).toEqual 5
|
55
|
+
|
56
|
+
describe "reload", ->
|
57
|
+
it "reloads the collection with the original params", ->
|
58
|
+
respondWith server, "/api/posts?include=replies&parents_only=true&per_page=5&page=1", resultsFrom: "posts", data: { posts: [buildPost(message: "old post", reply_ids: [])] }
|
59
|
+
collection = base.data.loadCollection "posts", include: ["replies"], filters: { parents_only: "true" }, perPage: 5
|
60
|
+
server.respond()
|
61
|
+
expect(collection.lastFetchOptions.page).toEqual 1
|
62
|
+
expect(collection.lastFetchOptions.perPage).toEqual 5
|
63
|
+
expect(collection.lastFetchOptions.include).toEqual ["replies"]
|
64
|
+
server.responses = []
|
65
|
+
respondWith server, "/api/posts?include=replies&parents_only=true&per_page=5&page=1", resultsFrom: "posts", data: { posts: [buildPost(message: "new post", reply_ids: [])] }
|
66
|
+
expect(collection.models[0].get("message")).toEqual "old post"
|
67
|
+
resetCounter = jasmine.createSpy("resetCounter")
|
68
|
+
loadedCounter = jasmine.createSpy("loadedCounter")
|
69
|
+
callback = jasmine.createSpy("callback spy")
|
70
|
+
collection.bind "reset", resetCounter
|
71
|
+
collection.bind "loaded", loadedCounter
|
72
|
+
|
73
|
+
collection.reload success: callback
|
74
|
+
|
75
|
+
expect(collection.loaded).toBe false
|
76
|
+
expect(collection.length).toEqual 0
|
77
|
+
server.respond()
|
78
|
+
expect(collection.length).toEqual 1
|
79
|
+
expect(collection.models[0].get("message")).toEqual "new post"
|
80
|
+
expect(resetCounter.callCount).toEqual 1
|
81
|
+
expect(loadedCounter.callCount).toEqual 1
|
82
|
+
expect(callback).toHaveBeenCalledWith(collection)
|
83
|
+
|
84
|
+
describe "getWithAssocation", ->
|
85
|
+
it "defaults to the regular get", ->
|
86
|
+
spyOn(collection, 'get')
|
87
|
+
collection.getWithAssocation(10)
|
88
|
+
expect(collection.get).toHaveBeenCalledWith(10)
|
89
|
+
|
90
|
+
describe 'setLoaded', ->
|
91
|
+
it "should set the values of @loaded", ->
|
92
|
+
collection.setLoaded true
|
93
|
+
expect(collection.loaded).toEqual(true)
|
94
|
+
collection.setLoaded false
|
95
|
+
expect(collection.loaded).toEqual(false)
|
96
|
+
|
97
|
+
it "triggers 'loaded' when becoming true", ->
|
98
|
+
spy = jasmine.createSpy()
|
99
|
+
collection.bind "loaded", spy
|
100
|
+
collection.setLoaded false
|
101
|
+
expect(spy).not.toHaveBeenCalled()
|
102
|
+
collection.setLoaded true
|
103
|
+
expect(spy).toHaveBeenCalled()
|
104
|
+
|
105
|
+
it "doesn't trigger loaded if trigger: false is provided", ->
|
106
|
+
spy = jasmine.createSpy()
|
107
|
+
collection.bind "loaded", spy
|
108
|
+
collection.setLoaded true, trigger: false
|
109
|
+
expect(spy).not.toHaveBeenCalled()
|
110
|
+
|
111
|
+
it "returns self", ->
|
112
|
+
spy = jasmine.createSpy()
|
113
|
+
collection.bind "loaded", spy
|
114
|
+
collection.setLoaded true
|
115
|
+
expect(spy).toHaveBeenCalledWith(collection)
|
116
|
+
|
117
|
+
describe "toServerJSON", ->
|
118
|
+
it "calls through to toJSON", ->
|
119
|
+
spy = spyOn(collection, 'toJSON')
|
120
|
+
collection.toServerJSON()
|
121
|
+
expect(spy).toHaveBeenCalled()
|
122
|
+
|
123
|
+
describe "ordering and filtering", ->
|
124
|
+
beforeEach ->
|
125
|
+
collection = new Brainstem.Collection([
|
126
|
+
new Brainstem.Model(id: 2, title: "Alpha", updated_at: 2, cool: false),
|
127
|
+
new Brainstem.Model(id: 3, title: "Beta", updated_at: 10, cool: true),
|
128
|
+
new Brainstem.Model(id: 4, title: "Gamma", updated_at: 5, cool: false),
|
129
|
+
new Brainstem.Model(id: 6, title: "Gamma", updated_at: 5, cool: false),
|
130
|
+
new Brainstem.Model(id: 5, title: "Gamma", updated_at: 4, cool: true)
|
131
|
+
])
|
132
|
+
|
133
|
+
describe "@getComparatorWithIdFailover", ->
|
134
|
+
it "returns a comparator that works for numerical ordering of unix timestamps, failing over to id when they're the same", ->
|
135
|
+
newCollection = new Brainstem.Collection collection.models, comparator: Brainstem.Collection.getComparatorWithIdFailover("updated_at:desc")
|
136
|
+
newCollection.sort()
|
137
|
+
expect(newCollection.pluck("id")).toEqual [3, 6, 4, 5, 2]
|
138
|
+
|
139
|
+
newCollection = new Brainstem.Collection collection.models, comparator: Brainstem.Collection.getComparatorWithIdFailover("updated_at:asc")
|
140
|
+
newCollection.sort()
|
141
|
+
expect(newCollection.pluck("id")).toEqual [2, 5, 4, 6, 3]
|