array attribute #458

Closed
wants to merge 6 commits into
from
@@ -1,4 +1,5 @@
var get = Ember.get;
+var set = Ember.set;
require("ember-data/system/model/model");
@@ -48,13 +49,61 @@ DS.attr = function(type, options) {
};
return Ember.computed(function(key, value) {
- var data;
+ function observeArray(arr, path, target) {
+ arr.addObserver('[]', this, function() {
+ target.setProperty(path, Ember.copy(arr));
+ });
+ }
+
+ function observeObject(proxy, path, child) {
+ function triggerChanges() {
+ this.triggerChanges();
+ }
+
+ if (child === undefined) child = get(proxy, 'content');
+
+ for (var key in child) {
+ var pathToVal = path.fmt(key);
+ var val = get(proxy, pathToVal);
+ if (typeof val === 'object') {
+ if (Ember.isArray(val)) {
+ observeArray(val, pathToVal, proxy.get('record'));
+ } else {
+ var childPath = pathToVal + '.%@';
+ observeObject(proxy, childPath, val);
+ }
+ } else {
+ proxy.addObserver(pathToVal, proxy, triggerChanges);
+ }
+ }
+ }
if (arguments.length === 2) {
Ember.assert("You may not set `id` as an attribute on your model. Please remove any lines that look like: `id: DS.attr('<type>')` from " + this.toString(), key !== 'id');
this.setProperty(key, value);
} else {
value = getAttr(this, options, key);
+ if (Ember.isArray(value)) {
+ observeArray(value, key, this);
+ } else if (typeof value === 'object') {
+ var proxyKey = '%@Proxy'.fmt(key);
+ if (get(this, proxyKey) === undefined) {
+ var record = this;
+ var proxy = Ember.ObjectProxy.create({
+ content: Ember.Object.create(value),
+ record: record,
+ triggerChanges: function() {
+ this.get('record').setProperty(key, this.get('content'));
+ }
+ });
+
+ observeObject(proxy, 'content.%@');
+
+ set(this, proxyKey, proxy);
+ }
+
+ value = get(this, proxyKey);
+ }
}
return value;
@@ -0,0 +1,141 @@
+var get = Ember.get, set = Ember.set;
+var Person;
+
+var array, store;
+
+var testSerializer = DS.Serializer.create({
+ primaryKey: function() {
+ return 'id';
+ }
+});
+
+var TestAdapter = DS.Adapter.extend({
+ serializer: testSerializer
+});
+
+module("DS.arrayAttr", {
+ setup: function() {
+ array = [
+ { id: '1', name: "Scumbag Dale", problems: ['booze'] },
+ { id: '2', name: "Scumbag Katz", problems: ['skag'] },
+ { id: '3', name: "Scumbag Bryn",}
+ ];
+
+ store = DS.Store.create({
+ adapter: TestAdapter.create()
+ });
+
+ store.adapter.registerTransform('string[]', {
+ fromJSON: function(serialized) {
+ return serialized;
+ },
+
+ toJSON: function(deserialized) {
+ return JSON.stringify(deserialized);
+ }
+ });
+
+ Person = DS.Model.extend({
+ name: DS.attr('string'),
+ problems: DS.attr('string[]', {defaultValue: []})
+ });
+
+ store.loadMany(Person, [1,2,3], array);
+ },
+
+ teardown: function() {
+ Person = null;
+ set(DS, 'defaultStore', null);
+ array = null;
+ }
+});
+
+test('the record should become dirty when array properties change', function() {
+ var dale = store.find(Person, 1);
+ equal(get(dale, 'problems')[0], 'booze', 'the array has some values');
+
+ equal(get(dale, 'isDirty'), false, 'the array is not dirty yet');
+
+ get(dale, 'problems').pushObject('cash flow');
+ equal(get(dale, 'problems.length'), 2, 'less money mo problems');
+ equal(get(dale, 'isDirty'), true, 'the model should be dirty now.');
+
+ var bryn = store.find(Person, 3);
+ equal(get(bryn, 'isDirty'), false, 'Bryn should still be clean');
+ get(bryn, 'problems').pushObject('laydeez');
+ equal(get(bryn, 'problems')[0], 'laydeez', 'Default array should be present');
+ ok(get(bryn, 'isDirty'), 'Pushing an object to the default should make it dirty');
+});
+
+module('DS.objectAttr', {
+ setup: function() {
+ array = [
+ { id: '1', name: "Scumbag Dale", bio: {
+ age: 80
+ }
+ },
+ { id: '2', name: "Scumbag Katz", bio: {
+ age: 75,
+ nested: {
+ tested: false
+ }
+ }
+ },
+ { id: '3', name: "Scumbag Bryn", bio: {
+ age: 99,
+ nested: {
+ array: ['one']
+ }
+ }}
+ ];
+
+ store = DS.Store.create({
+ adapter: TestAdapter.create()
+ });
+
+ store.adapter.registerTransform('object', {
+ fromJSON: function(serialized) {
+ return serialized;
+ },
+
+ toJSON: function(deserialized) {
+ return JSON.stringify(deserialized);
+ }
+ });
+
+ Person = DS.Model.extend({
+ name: DS.attr('string'),
+ bio: DS.attr('object', {defaultValue: {}})
+ });
+
+ store.loadMany(Person, [1,2,3], array);
+ },
+
+ teardown: function() {
+ Person = null;
+ set(DS, 'defaultStore', null);
+ array = null;
+ }
+});
+
+test('Changes to nested objects cause the model to become dirty', function() {
+
+ var dale = store.find(Person, 1);
+
+ set(dale, 'bio.age', 17);
+ equal(get(dale, 'bio.age'), 17, 'you can set a nested object value');
+ ok(dale.get('isDirty'), 'modifying a nested object makes dale dirty');
+
+ var katz = store.find(Person, 2);
+
+ set(katz, 'bio.nested.tested', true);
+ ok(get(katz, 'bio.nested.tested'), 'the deeper level has been set');
+ ok(get(katz, 'isDirty'), 'dirty at deeper levels');
+
+ var bryn = store.find(Person, 3);
+
+ get(bryn, 'bio.nested.array').pushObject('two');
+ ok(get(bryn, 'isDirty'), 'modifying an array in a nested object throws dirt');
+ equal(get(bryn, 'bio.nested.array')[1], 'two', 'the changes appear in the array');
+
+});
@@ -18,9 +18,19 @@ module("DS.Model", {
adapter: TestAdapter.create()
});
+ store.adapter.registerTransform('[]', {
+ fromJSON: function(serialized) {
+ return serialized;
+ },
+
+ toJSON: function(deserialized) {
+ return JSON.stringify(deserialized);
+ }
+ });
Person = DS.Model.extend({
name: DS.attr('string'),
- isDrugAddict: DS.attr('boolean')
+ isDrugAddict: DS.attr('boolean'),
+ convictions: DS.attr('[]')
});
},
@@ -105,6 +115,8 @@ test("a DS.Model can update its attributes", function() {
set(person, 'name', "Brohuda Katz");
equal(get(person, 'name'), "Brohuda Katz", "setting took hold");
+ set(person, 'convictions', ['petty theft']);
+ equal('petty theft', get(person, 'convictions')[0], 'array attr is set');
});
test("a DS.Model can have a defaultValue", function() {