brainstem-js 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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]
|