ember-data-factory-guy 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/Gruntfile.js +45 -0
- data/README.md +118 -0
- data/bower.json +35 -0
- data/dist/ember-data-factory-guy.js +376 -0
- data/dist/ember-data-factory-guy.min.js +1 -0
- data/ember-data-factory-guy.gemspec +20 -0
- data/lib/ember-data-factory-guy.rb +13 -0
- data/package.json +41 -0
- data/src/factory_guy.js +144 -0
- data/src/factory_guy_helper_mixin.js +89 -0
- data/src/has_many.js +188 -0
- data/src/store.js +142 -0
- data/tests/active_model_adapter_factory_test.js +63 -0
- data/tests/factory_guy_test.js +0 -0
- data/tests/fixture_adapter_factory_test.js +134 -0
- data/tests/index.html +38 -0
- data/tests/rest_adapter_factory_test.js +61 -0
- data/tests/store_test.js +35 -0
- data/tests/support/factories/project_factory.js +3 -0
- data/tests/support/factories/user_factory.js +10 -0
- data/tests/support/models/project.js +4 -0
- data/tests/support/models/user.js +4 -0
- data/tests/support/test_helper.js +23 -0
- data/tests/test_setup.js +22 -0
- data/vendor/assets/javascripts/ember_data_factory_guy.js +376 -0
- data/vendor/assets/javascripts/factory_guy_has_many.js +188 -0
- metadata +76 -0
@@ -0,0 +1 @@
|
|
1
|
+
FactoryGuy=Ember.Object.reopenClass({fixtureStore:{},fixtureLookup:{},modelIds:{},define:function(model,config){var info=this.getModelInfo(model);for(key in config){var value=config[key];info[key]=value;if(key!="default"){this.fixtureLookup[key]=model}}this.modelIds[model]=0},getModelInfo:function(model){if(!this.fixtureStore[model]){this.fixtureStore[model]={}}return this.fixtureStore[model]},lookupModelForName:function(name){var model=this.fixtureLookup[name];if(!model){if(this.fixtureStore[name]){model=name}}return model},generateId:function(model){var lastId=this.modelIds[model]||0;this.modelIds[model]=lastId+1;return this.modelIds[model]},build:function(name,opts){var model=this.lookupModelForName(name);if(!model){throw new Error("can't find that factory named ["+name+"]")}var modelInfo=this.fixtureStore[model];var modelAttributes=modelInfo[name]||{};var defaultModelAttributes=modelInfo.default;var fixture=$.extend({},defaultModelAttributes,modelAttributes,opts);fixture.id=this.generateId(model);return fixture},resetModels:function(store){var typeMaps=store.typeMaps;if(store.usingFixtureAdapter()){for(typeKey in this.fixtureStore){var modelType=store.modelFor(typeKey);modelType.FIXTURES=[];store.unloadAll(modelType)}}else{for(model in typeMaps){if(typeMaps[model].type.typeKey!="user"){store.unloadAll(typeMaps[model].type)}}}this.modelIds={}},pushFixture:function(modelClass,fixture){if(!modelClass["FIXTURES"]){modelClass["FIXTURES"]=[]}modelClass["FIXTURES"].push(fixture);return fixture}});DS.Store.reopen({usingFixtureAdapter:function(){var adapter=this.adapterFor("application");return adapter instanceof DS.FixtureAdapter},makeFixture:function(name,options){var modelName=FactoryGuy.lookupModelForName(name);var fixture=FactoryGuy.build(name,options);var modelType=this.modelFor(modelName);if(this.usingFixtureAdapter()){this.setBelongsToFixturesAssociation(modelType,modelName,fixture);return FactoryGuy.pushFixture(modelType,fixture)}else{var self=this;var model;Em.run(function(){model=self.push(modelName,fixture);self.setBelongsToRestAssociation(modelType,modelName,model)});return model}},setBelongsToFixturesAssociation:function(modelType,modelName,parentFixture){var store=this;var adapter=this.adapterFor("application");var relationShips=Ember.get(modelType,"relationshipNames");if(relationShips.hasMany){relationShips.hasMany.forEach(function(relationship){var hasManyModel=store.modelFor(Em.String.singularize(relationship));if(parentFixture[relationship]){parentFixture[relationship].forEach(function(id){var hasManyfixtures=adapter.fixturesForType(hasManyModel);var fixture=adapter.findFixtureById(hasManyfixtures,id);fixture[modelName]=parentFixture.id})}})}},setBelongsToRestAssociation:function(modelType,modelName,parent){var relationShips=Ember.get(modelType,"relationshipNames");if(relationShips.hasMany){relationShips.hasMany.forEach(function(name){var children=parent.get(name);if(children.get("length")>0){children.forEach(function(child){child.set(modelName,parent)})}})}},pushPayload:function(type,payload){if(this.usingFixtureAdapter()){var model=this.modelFor(modelName);FactoryGuy.pushFixture(model,payload)}else{this._super(type,payload)}}});DS.FixtureAdapter.reopen({createRecord:function(store,type,record){var promise=this._super(store,type,record);promise.then(function(){var hasManyName=Ember.String.pluralize(type.typeKey);var relationShips=Ember.get(type,"relationshipNames");if(relationShips.belongsTo){}});return promise}});FactoryGuyHelperMixin=Em.Mixin.create({setup:function(app){this.set("container",app.__container__);return this},useFixtureAdapter:function(app){app.ApplicationAdapter=DS.FixtureAdapter;this.getStore().adapterFor("application").simulateRemoteResponse=false},find:function(type,id){return this.getStore().find(type,id)},make:function(name,opts){return this.getStore().makeFixture(name,opts)},getStore:function(){return this.get("container").lookup("store:main")},pushPayload:function(type,hash){return this.getStore().pushPayload(type,hash)},pushRecord:function(type,hash){return this.getStore().push(type,hash)},stubEndpointForHttpRequest:function(url,json,options){options=options||{};var request={url:url,dataType:"json",responseText:json,type:options.type||"GET",status:options.status||200};if(options.data){request.data=options.data}$.mockjax(request)},handleCreate:function(name,opts){var model=FactoryGuy.lookupModelForName(name);this.stubEndpointForHttpRequest("/"+Em.String.pluralize(model),this.buildAjaxResponse(name,opts),{type:"POST"})},buildAjaxResponse:function(name,opts){var fixture=FactoryGuy.build(name,opts);var model=FactoryGuy.lookupModelForName(name);var hash={};hash[model]=fixture;return hash},handleUpdate:function(root,id){this.stubEndpointForHttpRequest("/"+Em.String.pluralize(root)+"/"+id,"{}",{type:"PUT"})},handleDelete:function(root,id){this.stubEndpointForHttpRequest("/"+Em.String.pluralize(root)+"/"+id,{},{type:"DELETE"})},teardown:function(){FactoryGuy.resetModels(this.getStore())}});
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
Gem::Specification.new do |s|
|
3
|
+
s.name = "ember-data-factory-guy"
|
4
|
+
s.version = "0.1"
|
5
|
+
s.platform = Gem::Platform::RUBY
|
6
|
+
s.authors = ["daniel sudol", "alex opak"]
|
7
|
+
s.email = ["dansudol@yahoo.com"]
|
8
|
+
s.homepage = "http://rubygems.org/gems/ember-data-factory-guy"
|
9
|
+
s.summary = "Create Fixtures for Ember Data"
|
10
|
+
s.description = "Create Fixtures for Ember Data"
|
11
|
+
s.license = "MIT"
|
12
|
+
|
13
|
+
s.required_rubygems_version = ">= 1.3.6"
|
14
|
+
s.rubyforge_project = "ember-data-factory-guy"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {tests}/*`.split("\n")
|
18
|
+
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module EmberDataFixtureFactory
|
2
|
+
if defined? ::Rails::Engine
|
3
|
+
# auto wire assets as Rails Engine
|
4
|
+
class Rails < ::Rails::Engine
|
5
|
+
end
|
6
|
+
|
7
|
+
elsif defined? ::Sprockets
|
8
|
+
root_dir = File.expand_path(File.dirname(File.dirname(__FILE__)))
|
9
|
+
# Set up asset paths for Sprockets apps
|
10
|
+
p "root_dir #{root_dir} #{File.join(root_dir, "vendor", "assets", "javascripts")}"
|
11
|
+
::Sprockets.append_path File.join(root_dir, "vendor", "assets", "javascripts")
|
12
|
+
end
|
13
|
+
end
|
data/package.json
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
{
|
2
|
+
"name": "ember-data-factory-guy",
|
3
|
+
"version": "0.0.1",
|
4
|
+
"authors": [
|
5
|
+
"Opak Alex <opak.alexandr@gmail.com>",
|
6
|
+
"Daniel Sudol <dansudol@yahoo.com>"
|
7
|
+
],
|
8
|
+
"description": "Factory for testing Ember application",
|
9
|
+
"main": "dist/ember-data-factory-guy.js",
|
10
|
+
"keywords": [
|
11
|
+
"Factory",
|
12
|
+
"Ember",
|
13
|
+
"Data",
|
14
|
+
"FixtureAdapter"
|
15
|
+
],
|
16
|
+
"license": "MIT",
|
17
|
+
|
18
|
+
"bugs": {
|
19
|
+
"url": ""
|
20
|
+
},
|
21
|
+
|
22
|
+
"scripts": {
|
23
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
24
|
+
},
|
25
|
+
|
26
|
+
"repository": {
|
27
|
+
"type": "git",
|
28
|
+
"url": ""
|
29
|
+
},
|
30
|
+
|
31
|
+
"devDependencies": [
|
32
|
+
"grunt-contrib-coffee",
|
33
|
+
"grunt-contrib-concat",
|
34
|
+
"grunt-contrib-uglify",
|
35
|
+
"grunt-contrib-qunit"
|
36
|
+
],
|
37
|
+
|
38
|
+
"engines": {
|
39
|
+
"node": ">=v0.10.1"
|
40
|
+
}
|
41
|
+
}
|
data/src/factory_guy.js
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
FactoryGuy = Ember.Object.reopenClass({
|
2
|
+
fixtureStore: {},
|
3
|
+
fixtureLookup: {},
|
4
|
+
modelIds: {},
|
5
|
+
|
6
|
+
/**
|
7
|
+
```javascript
|
8
|
+
|
9
|
+
User = DS.Model.extend({
|
10
|
+
name: DS.attr('string'),
|
11
|
+
})
|
12
|
+
|
13
|
+
FactoryGuy.define('user', {
|
14
|
+
default: {
|
15
|
+
name: "Fred"
|
16
|
+
},
|
17
|
+
|
18
|
+
bob: {
|
19
|
+
name: "Bob"
|
20
|
+
}
|
21
|
+
});
|
22
|
+
|
23
|
+
```
|
24
|
+
|
25
|
+
For the User model, you can define fixtures like 'bob' or just use 'user'
|
26
|
+
and get default values.
|
27
|
+
|
28
|
+
And to get those fixtures you would call them this way:
|
29
|
+
|
30
|
+
FactoryGuy.build('user') or FactoryGuy.build('bob')
|
31
|
+
|
32
|
+
@param model the model to define
|
33
|
+
@param config your default and specific fixtures
|
34
|
+
*/
|
35
|
+
define: function (model, config) {
|
36
|
+
var info = this.getModelInfo(model);
|
37
|
+
for (key in config) {
|
38
|
+
var value = config[key];
|
39
|
+
info[key] = value;
|
40
|
+
if (key != 'default') {
|
41
|
+
this.fixtureLookup[key] = model;
|
42
|
+
}
|
43
|
+
}
|
44
|
+
// setup id
|
45
|
+
this.modelIds[model] = 0;
|
46
|
+
},
|
47
|
+
|
48
|
+
/**
|
49
|
+
|
50
|
+
@param model
|
51
|
+
*/
|
52
|
+
getModelInfo: function (model) {
|
53
|
+
if (!this.fixtureStore[model]) {
|
54
|
+
this.fixtureStore[model] = {};
|
55
|
+
}
|
56
|
+
return this.fixtureStore[model];
|
57
|
+
},
|
58
|
+
|
59
|
+
/**
|
60
|
+
|
61
|
+
@param name fixture name
|
62
|
+
@returns model associated with fixture name
|
63
|
+
*/
|
64
|
+
lookupModelForName: function (name) {
|
65
|
+
var model = this.fixtureLookup[name];
|
66
|
+
if (!model) {
|
67
|
+
if (this.fixtureStore[name]) {
|
68
|
+
model = name;
|
69
|
+
}
|
70
|
+
}
|
71
|
+
return model;
|
72
|
+
},
|
73
|
+
|
74
|
+
/**
|
75
|
+
Generate next id for model
|
76
|
+
*/
|
77
|
+
generateId: function (model) {
|
78
|
+
var lastId = this.modelIds[model] || 0;
|
79
|
+
this.modelIds[model] = lastId + 1;
|
80
|
+
return this.modelIds[model];
|
81
|
+
},
|
82
|
+
|
83
|
+
/**
|
84
|
+
Build fixtures for model or specific fixture name. For example:
|
85
|
+
|
86
|
+
FactoryGuy.build('user') for User model
|
87
|
+
FactoryGuy.build('bob') for User model with bob attributes
|
88
|
+
|
89
|
+
@param name fixture name
|
90
|
+
@param opts options that will override default fixture values
|
91
|
+
@returns {*}
|
92
|
+
*/
|
93
|
+
build: function (name, opts) {
|
94
|
+
var model = this.lookupModelForName(name);
|
95
|
+
if (!model) {
|
96
|
+
throw new Error("can't find that factory named [" + name + "]");
|
97
|
+
}
|
98
|
+
|
99
|
+
var modelInfo = this.fixtureStore[model];
|
100
|
+
var modelAttributes = modelInfo[name] || {};
|
101
|
+
var defaultModelAttributes = modelInfo.default;
|
102
|
+
var fixture = $.extend({}, defaultModelAttributes, modelAttributes, opts);
|
103
|
+
fixture.id = this.generateId(model);
|
104
|
+
return fixture;
|
105
|
+
},
|
106
|
+
|
107
|
+
/**
|
108
|
+
Clear model instances from FIXTURES array, and from store cache.
|
109
|
+
Reset the id sequence for the models back to zero.
|
110
|
+
*/
|
111
|
+
resetModels: function (store) {
|
112
|
+
var typeMaps = store.typeMaps;
|
113
|
+
if (store.usingFixtureAdapter()) {
|
114
|
+
for (typeKey in this.fixtureStore) {
|
115
|
+
var modelType = store.modelFor(typeKey);
|
116
|
+
modelType.FIXTURES = [];
|
117
|
+
store.unloadAll(modelType);
|
118
|
+
}
|
119
|
+
} else {
|
120
|
+
for (model in typeMaps) {
|
121
|
+
if (typeMaps[model].type.typeKey != 'user') {
|
122
|
+
// console.log(typeMaps[model].type.typeKey)
|
123
|
+
store.unloadAll(typeMaps[model].type);
|
124
|
+
}
|
125
|
+
}
|
126
|
+
}
|
127
|
+
this.modelIds = {}
|
128
|
+
},
|
129
|
+
|
130
|
+
/**
|
131
|
+
Push fixture to model's FIXTURES array.
|
132
|
+
Used when store's adapter is a DS.FixtureAdapter.
|
133
|
+
|
134
|
+
@param modelClass DS.Model type
|
135
|
+
@param fixture the fixture to add
|
136
|
+
*/
|
137
|
+
pushFixture: function (modelClass, fixture) {
|
138
|
+
if (!modelClass['FIXTURES']) {
|
139
|
+
modelClass['FIXTURES'] = [];
|
140
|
+
}
|
141
|
+
modelClass['FIXTURES'].push(fixture);
|
142
|
+
return fixture;
|
143
|
+
}
|
144
|
+
})
|
@@ -0,0 +1,89 @@
|
|
1
|
+
FactoryGuyHelperMixin = Em.Mixin.create({
|
2
|
+
|
3
|
+
setup: function(app) {
|
4
|
+
this.set('container', app.__container__);
|
5
|
+
return this;
|
6
|
+
},
|
7
|
+
|
8
|
+
useFixtureAdapter: function(app) {
|
9
|
+
app.ApplicationAdapter = DS.FixtureAdapter;
|
10
|
+
this.getStore().adapterFor('application').simulateRemoteResponse = false;
|
11
|
+
},
|
12
|
+
|
13
|
+
find: function(type, id) {
|
14
|
+
return this.getStore().find(type, id);
|
15
|
+
},
|
16
|
+
|
17
|
+
make: function(name, opts) {
|
18
|
+
return this.getStore().makeFixture(name, opts);
|
19
|
+
},
|
20
|
+
|
21
|
+
getStore: function () {
|
22
|
+
return this.get('container').lookup('store:main');
|
23
|
+
},
|
24
|
+
|
25
|
+
pushPayload: function(type, hash) {
|
26
|
+
return this.getStore().pushPayload(type, hash);
|
27
|
+
},
|
28
|
+
|
29
|
+
pushRecord: function(type, hash) {
|
30
|
+
return this.getStore().push(type, hash);
|
31
|
+
},
|
32
|
+
|
33
|
+
stubEndpointForHttpRequest: function (url, json, options) {
|
34
|
+
options = options || {};
|
35
|
+
var request = {
|
36
|
+
url: url,
|
37
|
+
dataType: 'json',
|
38
|
+
responseText: json,
|
39
|
+
type: options.type || 'GET',
|
40
|
+
status: options.status || 200
|
41
|
+
}
|
42
|
+
|
43
|
+
if (options.data) {
|
44
|
+
request.data = options.data
|
45
|
+
}
|
46
|
+
|
47
|
+
$.mockjax(request);
|
48
|
+
},
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Handling ajax POST for a model
|
52
|
+
*
|
53
|
+
* @param name of the fixture ( or model ) to create
|
54
|
+
* @param opts fixture options
|
55
|
+
*/
|
56
|
+
handleCreate: function (name, opts) {
|
57
|
+
var model = FactoryGuy.lookupModelForName(name);
|
58
|
+
this.stubEndpointForHttpRequest(
|
59
|
+
"/" + Em.String.pluralize(model),
|
60
|
+
this.buildAjaxResponse(name, opts),
|
61
|
+
{type: 'POST'}
|
62
|
+
)
|
63
|
+
},
|
64
|
+
|
65
|
+
buildAjaxResponse: function (name, opts) {
|
66
|
+
var fixture = FactoryGuy.build(name, opts);
|
67
|
+
var model = FactoryGuy.lookupModelForName(name);
|
68
|
+
var hash = {};
|
69
|
+
hash[model] = fixture;
|
70
|
+
return hash;
|
71
|
+
},
|
72
|
+
|
73
|
+
handleUpdate: function (root, id) {
|
74
|
+
this.stubEndpointForHttpRequest(
|
75
|
+
"/" + Em.String.pluralize(root) + "/" + id, {}, {type: 'PUT'}
|
76
|
+
)
|
77
|
+
},
|
78
|
+
|
79
|
+
handleDelete: function (root, id) {
|
80
|
+
this.stubEndpointForHttpRequest(
|
81
|
+
"/" + Em.String.pluralize(root) + "/" + id, {}, {type: 'DELETE'}
|
82
|
+
)
|
83
|
+
},
|
84
|
+
|
85
|
+
teardown: function () {
|
86
|
+
FactoryGuy.resetModels(this.getStore());
|
87
|
+
}
|
88
|
+
|
89
|
+
})
|
data/src/has_many.js
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
(function(){
|
2
|
+
var get = Ember.get, set = Ember.set, setProperties = Ember.setProperties;
|
3
|
+
|
4
|
+
function asyncHasMany(record, type, options, meta) {
|
5
|
+
var relationship = record._relationships[key],
|
6
|
+
promiseLabel = "DS: Async hasMany " + record + " : " + key;
|
7
|
+
|
8
|
+
if (!relationship) {
|
9
|
+
var resolver = Ember.RSVP.defer(promiseLabel);
|
10
|
+
relationship = buildRelationship(record, key, options, function(store, data) {
|
11
|
+
var link = data.links && data.links[key];
|
12
|
+
var rel;
|
13
|
+
if (link) {
|
14
|
+
rel = store.findHasMany(record, link, meta, resolver);
|
15
|
+
} else {
|
16
|
+
rel = store.findMany(record, data[key], meta.type, resolver);
|
17
|
+
}
|
18
|
+
// cache the promise so we can use it
|
19
|
+
// when we come back and don't need to rebuild
|
20
|
+
// the relationship.
|
21
|
+
set(rel, 'promise', resolver.promise);
|
22
|
+
return rel;
|
23
|
+
});
|
24
|
+
}
|
25
|
+
|
26
|
+
var promise = relationship.get('promise').then(function() {
|
27
|
+
return relationship;
|
28
|
+
}, null, "DS: Async hasMany records received");
|
29
|
+
|
30
|
+
return DS.PromiseArray.create({
|
31
|
+
promise: promise
|
32
|
+
});
|
33
|
+
}
|
34
|
+
|
35
|
+
function buildRelationship(record, key, options, callback) {
|
36
|
+
var rels = record._relationships;
|
37
|
+
|
38
|
+
if (rels[key]) { return rels[key]; }
|
39
|
+
|
40
|
+
var data = get(record, 'data'),
|
41
|
+
store = get(record, 'store');
|
42
|
+
|
43
|
+
var relationship = rels[key] = callback.call(record, store, data);
|
44
|
+
|
45
|
+
return setProperties(relationship, {
|
46
|
+
owner: record,
|
47
|
+
name: key,
|
48
|
+
isPolymorphic: options.polymorphic
|
49
|
+
});
|
50
|
+
}
|
51
|
+
|
52
|
+
function hasRelationship(type, options) {
|
53
|
+
options = options || {};
|
54
|
+
|
55
|
+
var meta = {
|
56
|
+
type: type,
|
57
|
+
isRelationship: true,
|
58
|
+
options: options,
|
59
|
+
kind: 'hasMany'
|
60
|
+
};
|
61
|
+
|
62
|
+
return Ember.computed('data', function(key) {
|
63
|
+
var adapter = this.store.adapterFor('application');
|
64
|
+
if (adapter.toString().match('Fixture')) {
|
65
|
+
var relationship = this._relationships[key],
|
66
|
+
promiseLabel = "DS: Async hasMany " + this + " : " + key;
|
67
|
+
|
68
|
+
if (!relationship) {
|
69
|
+
var resolver = Ember.RSVP.defer(promiseLabel);
|
70
|
+
relationship = buildRelationship(this, key, options, function(store, data) {
|
71
|
+
var link = data.links && data.links[key];
|
72
|
+
var rel;
|
73
|
+
if (link) {
|
74
|
+
rel = store.findHasMany(this, link, meta, resolver);
|
75
|
+
} else {
|
76
|
+
rel = store.findMany(this, data[key], meta.type, resolver);
|
77
|
+
}
|
78
|
+
// cache the promise so we can use it
|
79
|
+
// when we come back and don't need to rebuild
|
80
|
+
// the relationship.
|
81
|
+
set(rel, 'promise', resolver.promise);
|
82
|
+
return rel;
|
83
|
+
});
|
84
|
+
}
|
85
|
+
|
86
|
+
var promise = relationship.get('promise').then(function() {
|
87
|
+
return relationship;
|
88
|
+
}, null, "DS: Async hasMany records received");
|
89
|
+
|
90
|
+
return DS.PromiseArray.create({
|
91
|
+
promise: promise
|
92
|
+
});
|
93
|
+
}
|
94
|
+
|
95
|
+
return buildRelationship(this, key, options, function(store, data) {
|
96
|
+
var records = data[key];
|
97
|
+
Ember.assert("You looked up the '" + key + "' relationship on '" + this + "' but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (`DS.hasMany({ async: true })`)", Ember.A(records).everyProperty('isEmpty', false));
|
98
|
+
return store.findMany(this, data[key], meta.type);
|
99
|
+
});
|
100
|
+
}).meta(meta).readOnly();
|
101
|
+
}
|
102
|
+
|
103
|
+
/**
|
104
|
+
`DS.hasMany` is used to define One-To-Many and Many-To-Many
|
105
|
+
relationships on a [DS.Model](/api/data/classes/DS.Model.html).
|
106
|
+
|
107
|
+
`DS.hasMany` takes an optional hash as a second parameter, currently
|
108
|
+
supported options are:
|
109
|
+
|
110
|
+
- `async`: A boolean value used to explicitly declare this to be an async relationship.
|
111
|
+
- `inverse`: A string used to identify the inverse property on a related model.
|
112
|
+
|
113
|
+
#### One-To-Many
|
114
|
+
To declare a one-to-many relationship between two models, use
|
115
|
+
`DS.belongsTo` in combination with `DS.hasMany`, like this:
|
116
|
+
|
117
|
+
```javascript
|
118
|
+
App.Post = DS.Model.extend({
|
119
|
+
comments: DS.hasMany('comment')
|
120
|
+
});
|
121
|
+
|
122
|
+
App.Comment = DS.Model.extend({
|
123
|
+
post: DS.belongsTo('post')
|
124
|
+
});
|
125
|
+
```
|
126
|
+
|
127
|
+
#### Many-To-Many
|
128
|
+
To declare a many-to-many relationship between two models, use
|
129
|
+
`DS.hasMany`:
|
130
|
+
|
131
|
+
```javascript
|
132
|
+
App.Post = DS.Model.extend({
|
133
|
+
tags: DS.hasMany('tag')
|
134
|
+
});
|
135
|
+
|
136
|
+
App.Tag = DS.Model.extend({
|
137
|
+
posts: DS.hasMany('post')
|
138
|
+
});
|
139
|
+
```
|
140
|
+
|
141
|
+
#### Explicit Inverses
|
142
|
+
|
143
|
+
Ember Data will do its best to discover which relationships map to
|
144
|
+
one another. In the one-to-many code above, for example, Ember Data
|
145
|
+
can figure out that changing the `comments` relationship should update
|
146
|
+
the `post` relationship on the inverse because post is the only
|
147
|
+
relationship to that model.
|
148
|
+
|
149
|
+
However, sometimes you may have multiple `belongsTo`/`hasManys` for the
|
150
|
+
same type. You can specify which property on the related model is
|
151
|
+
the inverse using `DS.hasMany`'s `inverse` option:
|
152
|
+
|
153
|
+
```javascript
|
154
|
+
var belongsTo = DS.belongsTo,
|
155
|
+
hasMany = DS.hasMany;
|
156
|
+
|
157
|
+
App.Comment = DS.Model.extend({
|
158
|
+
onePost: belongsTo('post'),
|
159
|
+
twoPost: belongsTo('post'),
|
160
|
+
redPost: belongsTo('post'),
|
161
|
+
bluePost: belongsTo('post')
|
162
|
+
});
|
163
|
+
|
164
|
+
App.Post = DS.Model.extend({
|
165
|
+
comments: hasMany('comment', {
|
166
|
+
inverse: 'redPost'
|
167
|
+
})
|
168
|
+
});
|
169
|
+
```
|
170
|
+
|
171
|
+
You can also specify an inverse on a `belongsTo`, which works how
|
172
|
+
you'd expect.
|
173
|
+
|
174
|
+
@namespace
|
175
|
+
@method hasMany
|
176
|
+
@for DS
|
177
|
+
@param {String or DS.Model} type the model type of the relationship
|
178
|
+
@param {Object} options a hash of options
|
179
|
+
@return {Ember.computed} relationship
|
180
|
+
*/
|
181
|
+
DS.hasMany = function(type, options) {
|
182
|
+
if (typeof type === 'object') {
|
183
|
+
options = type;
|
184
|
+
type = undefined;
|
185
|
+
}
|
186
|
+
return hasRelationship(type, options);
|
187
|
+
}
|
188
|
+
}).call();
|