From f5047af3bca60b67e4c89c7ea2bd251ad63b82e7 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 2 Nov 2015 11:13:30 -0800 Subject: [PATCH 01/15] Improve error messages inside RJSUtil --- src/js_util.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js_util.hpp b/src/js_util.hpp index 6967f80a..c530e4e5 100644 --- a/src/js_util.hpp +++ b/src/js_util.hpp @@ -138,7 +138,7 @@ static inline JSValueRef RJSValidatedPropertyValue(JSContextRef ctx, JSObjectRef static inline JSObjectRef RJSValidatedObjectProperty(JSContextRef ctx, JSObjectRef object, JSStringRef property, const char *err = NULL) { JSValueRef propertyValue = RJSValidatedPropertyValue(ctx, object, property); if (JSValueIsUndefined(ctx, propertyValue)) { - throw std::runtime_error(err ?: "Object property is undefined"); + throw std::runtime_error(err ?: "Object property '" + RJSStringForJSString(property) + "' is undefined"); } return RJSValidatedValueToObject(ctx, propertyValue, err); } @@ -158,7 +158,7 @@ static inline std::string RJSValidatedStringProperty(JSContextRef ctx, JSObjectR if (exception) { throw RJSException(ctx, exception); } - return RJSValidatedStringForValue(ctx, propertyValue); + return RJSValidatedStringForValue(ctx, propertyValue, RJSStringForJSString(property).c_str()); } static inline size_t RJSValidatedListLength(JSContextRef ctx, JSObjectRef object) { From 213c3d832f1a0b0cdd54fcb31b0941479a7d06fb Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Wed, 28 Oct 2015 23:20:26 -0700 Subject: [PATCH 02/15] Make RJSStringForValue use consistent exception style --- src/js_util.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/js_util.cpp b/src/js_util.cpp index 98862bdf..c9c9a96c 100644 --- a/src/js_util.cpp +++ b/src/js_util.cpp @@ -47,10 +47,10 @@ std::string RJSStringForJSString(JSStringRef jsString) { } std::string RJSStringForValue(JSContextRef ctx, JSValueRef value) { - JSValueRef *exception; - JSStringRef jsString = JSValueToStringCopy(ctx, value, exception); + JSValueRef exception = NULL; + JSStringRef jsString = JSValueToStringCopy(ctx, value, &exception); if (!jsString) { - throw RJSException(ctx, *exception); + throw RJSException(ctx, exception); } std::string string = RJSStringForJSString(jsString); @@ -97,4 +97,3 @@ bool RJSIsValueDate(JSContextRef ctx, JSValueRef value) { static JSStringRef dateString = JSStringCreateWithUTF8CString("Date"); return RJSIsValueObjectOfType(ctx, value, dateString); } - From e86dc94ee9d9f15e487ce7d4823e5c3b3832589d Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 2 Nov 2015 11:59:33 -0800 Subject: [PATCH 03/15] Plug small leak from not releasing a JSStringRef --- src/js_object.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/js_object.cpp b/src/js_object.cpp index 0938b493..2112df42 100644 --- a/src/js_object.cpp +++ b/src/js_object.cpp @@ -65,13 +65,16 @@ JSObjectRef RJSObjectCreate(JSContextRef ctx, Object object) { template<> bool RJSAccessor::dict_has_value_for_key(JSContextRef ctx, JSValueRef dict, const std::string &prop_name) { JSObjectRef object = RJSValidatedValueToObject(ctx, dict); - JSStringRef propStr =JSStringCreateWithUTF8CString(prop_name.c_str()); - return JSObjectHasProperty(ctx, object, propStr); + JSStringRef propStr = RJSStringForString(prop_name); + bool ret = JSObjectHasProperty(ctx, object, propStr); + + JSStringRelease(propStr); + return ret; } template<> JSValueRef RJSAccessor::dict_value_for_key(JSContextRef ctx, JSValueRef dict, const std::string &prop_name) { JSObjectRef object = RJSValidatedValueToObject(ctx, dict); - JSStringRef propStr =JSStringCreateWithUTF8CString(prop_name.c_str()); + JSStringRef propStr = RJSStringForString(prop_name); JSValueRef ex = NULL; JSValueRef ret = JSObjectGetProperty(ctx, object, propStr, &ex); if (ex) { From 358e5dacf3ddb0f8d530b7453c0f68cf07fc3e15 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 2 Nov 2015 16:29:13 -0800 Subject: [PATCH 04/15] Add method to clear mutation listeners in RPC client --- lib/realm.js | 5 ++++- lib/util.js | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/realm.js b/lib/realm.js index d9eeb336..aecc56de 100644 --- a/lib/realm.js +++ b/lib/realm.js @@ -125,7 +125,10 @@ Object.defineProperties(Realm, { set: util.setterForProperty('defaultPath'), }, clearTestState: { - value: rpc.clearTestState, + value: function() { + util.clearMutationListeners(); + rpc.clearTestState(); + }, }, }); diff --git a/lib/util.js b/lib/util.js index 6ea4f95c..5733e54d 100644 --- a/lib/util.js +++ b/lib/util.js @@ -8,9 +8,10 @@ const constants = require('./constants'); const rpc = require('./rpc'); const {keys} = constants; -const mutationListeners = {}; +let mutationListeners = {}; module.exports = { + clearMutationListeners, fireMutationListeners, createList, createMethods, @@ -31,6 +32,10 @@ function removeMutationListener(realmId, callback) { } } +function clearMutationListeners() { + mutationListeners = {}; +} + function fireMutationListeners(realmId) { let listeners = mutationListeners[realmId]; if (listeners) { From 43e14093cc4372e839ac3b3bd2550d000b8815cc Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Fri, 30 Oct 2015 12:08:31 -0700 Subject: [PATCH 05/15] Change schema API to take properties as an object The keys are names of the properties, which is more natural in JS, but will cause issues with ability to create objects where arrays of values. This feature will be removed in a subsequent commit. --- src/js_init.cpp | 2 +- src/js_schema.cpp | 107 ++++++++++++++++++++++-------- tests/lib/object-tests.js | 14 ++-- tests/lib/realm-tests.js | 22 ++++--- tests/lib/schemas.js | 132 +++++++++++++++++++------------------- 5 files changed, 166 insertions(+), 111 deletions(-) diff --git a/src/js_init.cpp b/src/js_init.cpp index 9dbb67ee..914f6f0a 100644 --- a/src/js_init.cpp +++ b/src/js_init.cpp @@ -32,7 +32,7 @@ JSClassRef RJSRealmTypeClass() { { "DATE", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "DATA", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "OBJECT", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, - { "LIST", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, + { "LIST", RJSTypeGet, NULL, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { NULL, NULL, NULL, 0 } }; realmTypesDefinition.staticValues = types; diff --git a/src/js_schema.cpp b/src/js_schema.cpp index 3bf95747..9873f991 100644 --- a/src/js_schema.cpp +++ b/src/js_schema.cpp @@ -31,25 +31,34 @@ JSObjectRef RJSSchemaCreate(JSContextRef ctx, Schema &schema) { return RJSWrapObject(ctx, RJSSchemaClass(), wrapper); } -static inline Property RJSParseProperty(JSContextRef ctx, JSObjectRef propertyObject) { - static JSStringRef nameString = JSStringCreateWithUTF8CString("name"); +static inline Property RJSParseProperty(JSContextRef ctx, JSValueRef propertyAttributes, std::string propertyName, ObjectDefaults &objectDefaults) { + static JSStringRef defaultString = JSStringCreateWithUTF8CString("default"); static JSStringRef typeString = JSStringCreateWithUTF8CString("type"); static JSStringRef objectTypeString = JSStringCreateWithUTF8CString("objectType"); static JSStringRef optionalString = JSStringCreateWithUTF8CString("optional"); Property prop; - prop.name = RJSValidatedStringProperty(ctx, propertyObject, nameString); + prop.name = propertyName; - prop.is_nullable = false; - JSValueRef optionalValue = JSObjectGetProperty(ctx, propertyObject, optionalString, NULL); - if (!JSValueIsUndefined(ctx, optionalValue)) { - if (!JSValueIsBoolean(ctx, optionalValue)) { - throw std::runtime_error("'optional' designation expected to be of type boolean"); + JSObjectRef propertyObject = NULL; + std::string type; + + if (JSValueIsObject(ctx, propertyAttributes)) { + propertyObject = RJSValidatedValueToObject(ctx, propertyAttributes); + type = RJSValidatedStringProperty(ctx, propertyObject, typeString); + + JSValueRef optionalValue = JSObjectGetProperty(ctx, propertyObject, optionalString, NULL); + if (!JSValueIsUndefined(ctx, optionalValue)) { + if (!JSValueIsBoolean(ctx, optionalValue)) { + throw std::runtime_error("'optional' designation expected to be of type boolean"); + } + prop.is_nullable = JSValueToBoolean(ctx, optionalValue); } - prop.is_nullable = JSValueToBoolean(ctx, optionalValue); + } + else { + type = RJSValidatedStringForValue(ctx, propertyAttributes); } - std::string type = RJSValidatedStringProperty(ctx, propertyObject, typeString); if (type == "bool") { prop.type = PropertyTypeBool; } @@ -72,14 +81,36 @@ static inline Property RJSParseProperty(JSContextRef ctx, JSObjectRef propertyOb prop.type = PropertyTypeData; } else if (type == "list") { + if (!propertyObject) { + throw std::runtime_error("List property must specify 'objectType'"); + } prop.type = PropertyTypeArray; prop.object_type = RJSValidatedStringProperty(ctx, propertyObject, objectTypeString); } else { prop.type = PropertyTypeObject; prop.is_nullable = true; - prop.object_type = type == "object" ? RJSValidatedStringProperty(ctx, propertyObject, objectTypeString) : type; + + // The type could either be 'object' or the name of another object type in the same schema. + if (type == "object") { + if (!propertyObject) { + throw std::runtime_error("Object property must specify 'objectType'"); + } + prop.object_type = RJSValidatedStringProperty(ctx, propertyObject, objectTypeString); + } + else { + prop.object_type = type; + } } + + if (propertyObject) { + JSValueRef defaultValue = RJSValidatedPropertyValue(ctx, propertyObject, defaultString); + if (!JSValueIsUndefined(ctx, defaultValue)) { + JSValueProtect(ctx, defaultValue); + objectDefaults.emplace(prop.name, defaultValue); + } + } + return prop; } @@ -88,6 +119,7 @@ static inline ObjectSchema RJSParseObjectSchema(JSContextRef ctx, JSObjectRef ob static JSStringRef prototypeString = JSStringCreateWithUTF8CString("prototype"); JSObjectRef prototypeObject = NULL; JSValueRef prototypeValue = RJSValidatedPropertyValue(ctx, objectSchemaObject, prototypeString); + if (!JSValueIsUndefined(ctx, prototypeValue)) { prototypeObject = RJSValidatedValueToObject(ctx, prototypeValue); objectSchemaObject = RJSValidatedObjectProperty(ctx, prototypeObject, schemaString, "Realm object prototype must have a 'schema' property."); @@ -99,27 +131,46 @@ static inline ObjectSchema RJSParseObjectSchema(JSContextRef ctx, JSObjectRef ob } } - static JSStringRef propertiesString = JSStringCreateWithUTF8CString("properties"); - JSObjectRef propertiesObject = RJSValidatedObjectProperty(ctx, objectSchemaObject, propertiesString, "ObjectSchema object must have a 'properties' array."); - - ObjectSchema objectSchema; - ObjectDefaults objectDefaults; static JSStringRef nameString = JSStringCreateWithUTF8CString("name"); + static JSStringRef propertiesString = JSStringCreateWithUTF8CString("properties"); + JSObjectRef propertiesObject = RJSValidatedObjectProperty(ctx, objectSchemaObject, propertiesString, "ObjectSchema must have a 'properties' object."); + + ObjectDefaults objectDefaults; + ObjectSchema objectSchema; objectSchema.name = RJSValidatedStringProperty(ctx, objectSchemaObject, nameString); - size_t numProperties = RJSValidatedListLength(ctx, propertiesObject); - for (unsigned int p = 0; p < numProperties; p++) { - JSObjectRef property = RJSValidatedObjectAtIndex(ctx, propertiesObject, p); - objectSchema.properties.emplace_back(RJSParseProperty(ctx, property)); + JSPropertyNameArrayRef propertyNames = NULL; + size_t propertyCount; - static JSStringRef defaultString = JSStringCreateWithUTF8CString("default"); - JSValueRef defaultValue = JSObjectGetProperty(ctx, property, defaultString, NULL); - if (!JSValueIsUndefined(ctx, defaultValue)) { - JSValueProtect(ctx, defaultValue); - objectDefaults.emplace(objectSchema.properties.back().name, defaultValue); - } + if (RJSIsValueArray(ctx, propertiesObject)) { + propertyCount = RJSValidatedListLength(ctx, propertiesObject); + } + else { + propertyNames = JSObjectCopyPropertyNames(ctx, propertiesObject); + propertyCount = JSPropertyNameArrayGetCount(propertyNames); + } + + for (size_t i = 0; i < propertyCount; i++) { + Property property; + + // Check if the properties were provided as an object. + if (propertyNames) { + JSStringRef propertyName = JSPropertyNameArrayGetNameAtIndex(propertyNames, i); + JSValueRef propertyValue = RJSValidatedPropertyValue(ctx, propertiesObject, propertyName); + property = RJSParseProperty(ctx, propertyValue, RJSStringForJSString(propertyName), objectDefaults); + } + else { + JSObjectRef propertyObject = RJSValidatedObjectAtIndex(ctx, propertiesObject, (unsigned int)i); + std::string propertyName = RJSValidatedStringProperty(ctx, propertyObject, nameString); + property = RJSParseProperty(ctx, propertyObject, propertyName, objectDefaults); + } + + objectSchema.properties.emplace_back(property); + } + + if (propertyNames) { + JSPropertyNameArrayRelease(propertyNames); } - defaults.emplace(objectSchema.name, std::move(objectDefaults)); static JSStringRef primaryString = JSStringCreateWithUTF8CString("primaryKey"); JSValueRef primaryValue = RJSValidatedPropertyValue(ctx, objectSchemaObject, primaryString); @@ -138,6 +189,8 @@ static inline ObjectSchema RJSParseObjectSchema(JSContextRef ctx, JSObjectRef ob prototypes[objectSchema.name] = std::move(prototypeObject); } + defaults.emplace(objectSchema.name, std::move(objectDefaults)); + return objectSchema; } diff --git a/tests/lib/object-tests.js b/tests/lib/object-tests.js index 92e1b830..4bdb49df 100644 --- a/tests/lib/object-tests.js +++ b/tests/lib/object-tests.js @@ -163,15 +163,15 @@ module.exports = BaseTest.extend({ realm.write(function() { obj = realm.create('NullableBasicTypesObject', basicTypesValues); obj1 = realm.create('NullableBasicTypesObject', basicTypesValues); - for (var prop of schemas.NullableBasicTypes.properties) { - obj[prop.name] = null; - obj1[prop.name] = undefined; + for (var name in schemas.NullableBasicTypes.properties) { + obj[name] = null; + obj1[name] = undefined; } }); - for (var prop of schemas.NullableBasicTypes.properties) { - TestCase.assertEqual(obj[prop.name], null); - TestCase.assertEqual(obj1[prop.name], null); + for (var name in schemas.NullableBasicTypes.properties) { + TestCase.assertEqual(obj[name], null); + TestCase.assertEqual(obj1[name], null); } realm.write(function() { @@ -262,7 +262,7 @@ module.exports = BaseTest.extend({ object = realm.create('BasicTypesObject', basicTypesValues); }); - var propNames = schemas.BasicTypes.properties.map(function(prop) { return prop.name; }); + var propNames = Object.keys(schemas.BasicTypes.properties); TestCase.assertArraysEqual(Object.keys(object), propNames, 'Object.keys'); for (var key in object) { diff --git a/tests/lib/realm-tests.js b/tests/lib/realm-tests.js index 32bb20b7..3290b15a 100644 --- a/tests/lib/realm-tests.js +++ b/tests/lib/realm-tests.js @@ -162,17 +162,19 @@ module.exports = BaseTest.extend({ var realm = new Realm({schema: [schemas.DefaultValues, schemas.TestObject]}); realm.write(function() { var obj = realm.create('DefaultValuesObject', {}); - TestCase.assertEqual(obj.boolCol, schemas.DefaultValues.properties[0].default); - TestCase.assertEqual(obj.intCol, schemas.DefaultValues.properties[1].default); - TestCase.assertEqualWithTolerance(obj.floatCol, schemas.DefaultValues.properties[2].default, 0.000001); - TestCase.assertEqual(obj.doubleCol, schemas.DefaultValues.properties[3].default); - TestCase.assertEqual(obj.stringCol, schemas.DefaultValues.properties[4].default); - TestCase.assertEqual(obj.dateCol.getTime(), schemas.DefaultValues.properties[5].default.getTime()); - TestCase.assertEqual(obj.dataCol.byteLength, schemas.DefaultValues.properties[6].default.byteLength); - TestCase.assertEqual(obj.objectCol.doubleCol, schemas.DefaultValues.properties[7].default[0]); + var properties = schemas.DefaultValues.properties; + + TestCase.assertEqual(obj.boolCol, properties.boolCol.default); + TestCase.assertEqual(obj.intCol, properties.intCol.default); + TestCase.assertEqualWithTolerance(obj.floatCol, properties.floatCol.default, 0.000001); + TestCase.assertEqual(obj.doubleCol, properties.doubleCol.default); + TestCase.assertEqual(obj.stringCol, properties.stringCol.default); + TestCase.assertEqual(obj.dateCol.getTime(), properties.dateCol.default.getTime()); + TestCase.assertEqual(obj.dataCol.byteLength, properties.dataCol.default.byteLength); + TestCase.assertEqual(obj.objectCol.doubleCol, properties.objectCol.default[0]); TestCase.assertEqual(obj.nullObjectCol, null); - TestCase.assertEqual(obj.arrayCol.length, schemas.DefaultValues.properties[9].default.length); - TestCase.assertEqual(obj.arrayCol[0].doubleCol, schemas.DefaultValues.properties[9].default[0][0]); + TestCase.assertEqual(obj.arrayCol.length, properties.arrayCol.default.length); + TestCase.assertEqual(obj.arrayCol[0].doubleCol, properties.arrayCol.default[0][0]); }); }, diff --git a/tests/lib/schemas.js b/tests/lib/schemas.js index 2aac328f..86ca9eba 100644 --- a/tests/lib/schemas.js +++ b/tests/lib/schemas.js @@ -7,20 +7,20 @@ var Realm = require('realm'); exports.TestObject = { - name: 'TestObject', - properties: [ - {name: 'doubleCol', type: Realm.Types.DOUBLE}, - ] + name: 'TestObject', + properties: { + doubleCol: Realm.Types.DOUBLE, + } }; function PersonObject() {} PersonObject.prototype.schema = { - name: 'PersonObject', - properties: [ - {name: 'name', type: Realm.Types.STRING}, - {name: 'age', type: Realm.Types.DOUBLE}, - {name: 'married', type: Realm.Types.BOOL, default: false} - ] + name: 'PersonObject', + properties: { + name: Realm.Types.STRING, + age: Realm.Types.DOUBLE, + married: {type: Realm.Types.BOOL, default: false}, + } }; PersonObject.prototype.description = function() { return this.name + ' ' + this.age; @@ -29,79 +29,79 @@ exports.PersonObject = PersonObject; exports.BasicTypes = { name: 'BasicTypesObject', - properties: [ - {name: 'boolCol', type: Realm.Types.BOOL}, - {name: 'intCol', type: Realm.Types.INT}, - {name: 'floatCol', type: Realm.Types.FLOAT}, - {name: 'doubleCol', type: Realm.Types.DOUBLE}, - {name: 'stringCol', type: Realm.Types.STRING}, - {name: 'dateCol', type: Realm.Types.DATE}, - {name: 'dataCol', type: Realm.Types.DATA}, - ] + properties: { + boolCol: Realm.Types.BOOL, + intCol: Realm.Types.INT, + floatCol: Realm.Types.FLOAT, + doubleCol: Realm.Types.DOUBLE, + stringCol: Realm.Types.STRING, + dateCol: Realm.Types.DATE, + dataCol: Realm.Types.DATA, + } }; exports.NullableBasicTypes = { name: 'NullableBasicTypesObject', - properties: [ - {name: 'boolCol', type: Realm.Types.BOOL, optional: true}, - {name: 'intCol', type: Realm.Types.INT, optional: true}, - {name: 'floatCol', type: Realm.Types.FLOAT, optional: true}, - {name: 'doubleCol', type: Realm.Types.DOUBLE, optional: true}, - {name: 'stringCol', type: Realm.Types.STRING, optional: true}, - {name: 'dateCol', type: Realm.Types.DATE, optional: true}, - {name: 'dataCol', type: Realm.Types.DATA, optional: true}, - ] + properties: { + boolCol: {type: Realm.Types.BOOL, optional: true}, + intCol: {type: Realm.Types.INT, optional: true}, + floatCol: {type: Realm.Types.FLOAT, optional: true}, + doubleCol: {type: Realm.Types.DOUBLE, optional: true}, + stringCol: {type: Realm.Types.STRING, optional: true}, + dateCol: {type: Realm.Types.DATE, optional: true}, + dataCol: {type: Realm.Types.DATA, optional: true}, + } }; exports.LinkTypes = { name: 'LinkTypesObject', - properties: [ - {name: 'objectCol', type: 'TestObject'}, - {name: 'objectCol1', type: Realm.Types.OBJECT, objectType: 'TestObject'}, - {name: 'arrayCol', type: Realm.Types.LIST, objectType: 'TestObject'}, - ] + properties: { + objectCol: 'TestObject', + objectCol1: {type: Realm.Types.OBJECT, objectType: 'TestObject'}, + arrayCol: {type: Realm.Types.LIST, objectType: 'TestObject'}, + } }; exports.IntPrimary = { - name: 'IntPrimaryObject', - primaryKey: 'primaryCol', - properties: [ - {name: 'primaryCol', type: Realm.Types.INT}, - {name: 'valueCol', type: Realm.Types.STRING}, - ] + name: 'IntPrimaryObject', + primaryKey: 'primaryCol', + properties: { + primaryCol: Realm.Types.INT, + valueCol: Realm.Types.STRING, + } }; exports.AllTypes = { - name: 'AllTypesObject', - primaryKey: 'primaryCol', - properties: [ - {name: 'primaryCol',type: Realm.Types.STRING}, - {name: 'boolCol', type: Realm.Types.BOOL}, - {name: 'intCol', type: Realm.Types.INT}, - {name: 'floatCol', type: Realm.Types.FLOAT}, - {name: 'doubleCol', type: Realm.Types.DOUBLE}, - {name: 'stringCol', type: Realm.Types.STRING}, - {name: 'dateCol', type: Realm.Types.DATE}, - {name: 'dataCol', type: Realm.Types.DATA}, - {name: 'objectCol', type: 'TestObject'}, - {name: 'arrayCol', type: Realm.Types.LIST, objectType: 'TestObject'}, - ] + name: 'AllTypesObject', + primaryKey: 'primaryCol', + properties: { + primaryCol: Realm.Types.STRING, + boolCol: Realm.Types.BOOL, + intCol: Realm.Types.INT, + floatCol: Realm.Types.FLOAT, + doubleCol: Realm.Types.DOUBLE, + stringCol: Realm.Types.STRING, + dateCol: Realm.Types.DATE, + dataCol: Realm.Types.DATA, + objectCol: 'TestObject', + arrayCol: {type: Realm.Types.LIST, objectType: 'TestObject'}, + } }; exports.DefaultValues = { - name: 'DefaultValuesObject', - properties: [ - {name: 'boolCol', type: Realm.Types.BOOL, default: true}, - {name: 'intCol', type: Realm.Types.INT, default: -1}, - {name: 'floatCol', type: Realm.Types.FLOAT, default: -1.1}, - {name: 'doubleCol', type: Realm.Types.DOUBLE, default: -1.11}, - {name: 'stringCol', type: Realm.Types.STRING, default: 'defaultString'}, - {name: 'dateCol', type: Realm.Types.DATE, default: new Date(1.111)}, - {name: 'dataCol', type: Realm.Types.DATA, default: new ArrayBuffer(1)}, - {name: 'objectCol', type: 'TestObject', default: [1]}, - {name: 'nullObjectCol', type: 'TestObject', default: null}, - {name: 'arrayCol', type: Realm.Types.LIST, objectType: 'TestObject', default: [[2]]}, - ] + name: 'DefaultValuesObject', + properties: { + boolCol: {type: Realm.Types.BOOL, default: true}, + intCol: {type: Realm.Types.INT, default: -1}, + floatCol: {type: Realm.Types.FLOAT, default: -1.1}, + doubleCol: {type: Realm.Types.DOUBLE, default: -1.11}, + stringCol: {type: Realm.Types.STRING, default: 'defaultString'}, + dateCol: {type: Realm.Types.DATE, default: new Date(1.111)}, + dataCol: {type: Realm.Types.DATA, default: new ArrayBuffer(1)}, + objectCol: {type: 'TestObject', default: [1]}, + nullObjectCol: {type: 'TestObject', default: null}, + arrayCol: {type: Realm.Types.LIST, objectType: 'TestObject', default: [[2]]}, + } }; exports.QueryObject = { From c928ab716ee281a74fdf5e0fc33b699662c8e298 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 2 Nov 2015 16:36:24 -0800 Subject: [PATCH 06/15] RPC now keeps object keys in the same order Maintaining insertion order when passing objects through the RPC is essential to make the new schema API work. --- lib/constants.js | 1 + lib/rpc.js | 25 ++++++++++++++++------- src/rpc.cpp | 53 +++++++++++++++++++++++++++++++++++------------- 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/lib/constants.js b/lib/constants.js index 063d3eef..ff538a0c 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -17,6 +17,7 @@ let propTypes = {}; }); [ + 'DICT', 'FUNCTION', 'REALM', 'RESULTS', diff --git a/lib/rpc.js b/lib/rpc.js index e69275dc..43fb140b 100644 --- a/lib/rpc.js +++ b/lib/rpc.js @@ -10,6 +10,7 @@ const constants = require('./constants'); const DEVICE_HOST = 'localhost:8082'; const {keys, objectTypes, propTypes} = constants; +const {id: idKey, realm: realmKey} = keys; const typeConverters = {}; let XMLHttpRequest = global.originalXMLHttpRequest || global.XMLHttpRequest; @@ -40,6 +41,7 @@ module.exports = { registerTypeConverter(propTypes.DATA, (_, {value}) => base64.decode(value)); registerTypeConverter(propTypes.DATE, (_, {value}) => new Date(value)); +registerTypeConverter(objectTypes.DICT, deserializeDict); function registerTypeConverter(type, handler) { typeConverters[type] = handler; @@ -106,9 +108,9 @@ function serialize(realmId, value) { return {value: value}; } - let id = value[keys.id]; + let id = value[idKey]; if (id) { - if (value[keys.realm] != realmId) { + if (value[realmKey] != realmId) { throw new Error('Unable to serialize value from another Realm'); } @@ -128,11 +130,9 @@ function serialize(realmId, value) { return {type: propTypes.DATA, value: base64.encode(value)}; } - let object = {}; - for (let key in value) { - object[key] = serialize(realmId, value[key]); - } - return {value: object}; + let keys = Object.keys(value); + let values = keys.map((key) => serialize(realmId, value[key])); + return {type: objectTypes.DICT, keys, values}; } function deserialize(realmId, info) { @@ -150,6 +150,17 @@ function deserialize(realmId, info) { return value; } +function deserializeDict(realmId, info) { + let {keys, values} = info; + let object = {}; + + for (let i = 0, len = keys.length; i < len; i++) { + object[keys[i]] = values[i]; + } + + return object; +} + function sendRequest(command, data) { data = Object.assign({}, data, sessionId ? {sessionId} : null); diff --git a/src/rpc.cpp b/src/rpc.cpp index ce270d5a..ba7d5a7e 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -22,6 +22,7 @@ using RJSAccessor = realm::NativeAccessor; using namespace realm_js; +static const char * const RealmObjectTypesDictionary = "ObjectTypesDICT"; static const char * const RealmObjectTypesFunction = "ObjectTypesFUNCTION"; static const char * const RealmObjectTypesResults = "ObjectTypesRESULTS"; @@ -262,6 +263,28 @@ json RPCServer::serialize_json_value(JSValueRef value) { {"value", RJSValidatedValueToNumber(m_context, value)}, }; } + else { + JSPropertyNameArrayRef js_keys = JSObjectCopyPropertyNames(m_context, js_object); + size_t count = JSPropertyNameArrayGetCount(js_keys); + std::vector keys; + std::vector values; + + for (size_t i = 0; i < count; i++) { + JSStringRef js_key = JSPropertyNameArrayGetNameAtIndex(js_keys, i); + JSValueRef js_value = RJSValidatedPropertyValue(m_context, js_object, js_key); + + keys.push_back(RJSStringForJSString(js_key)); + values.push_back(serialize_json_value(js_value)); + } + + JSPropertyNameArrayRelease(js_keys); + + return { + {"type", RealmObjectTypesDictionary}, + {"keys", keys}, + {"values", values}, + }; + } assert(0); } @@ -299,6 +322,22 @@ JSValueRef RPCServer::deserialize_json_value(const json dict) return js_function; } + else if (type_string == RealmObjectTypesDictionary) { + JSObjectRef js_object = JSObjectMake(m_context, NULL, NULL); + json keys = dict["keys"]; + json values = dict["values"]; + size_t count = keys.size(); + + for (size_t i = 0; i < count; i++) { + JSStringRef js_key = RJSStringForString(keys.at(i)); + JSValueRef js_value = deserialize_json_value(values.at(i)); + + JSObjectSetProperty(m_context, js_object, js_key, js_value, 0, NULL); + JSStringRelease(js_key); + } + + return js_object; + } else if (type_string == RJSTypeGet(realm::PropertyTypeData)) { std::string bytes; if (!base64_decode(value.get(), &bytes)) { @@ -344,19 +383,5 @@ JSValueRef RPCServer::deserialize_json_value(const json dict) return JSObjectMakeArray(m_context, count, js_values, NULL); } - else if (value.is_object()) { - JSObjectRef js_object = JSObjectMake(m_context, NULL, NULL); - - for (json::iterator it = value.begin(); it != value.end(); ++it) { - JSValueRef js_value = deserialize_json_value(it.value()); - JSStringRef js_key = JSStringCreateWithUTF8CString(it.key().c_str()); - - JSObjectSetProperty(m_context, js_object, js_key, js_value, 0, NULL); - JSStringRelease(js_key); - } - - return js_object; - } - assert(0); } From 36ffc6c77ccad7c1baba3eb720d0e28f5a5807a3 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 2 Nov 2015 21:54:05 -0800 Subject: [PATCH 07/15] Simplify object schema info returned from RPC Only the property names are needed. --- lib/objects.js | 4 +--- src/rpc.cpp | 8 +++----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/objects.js b/lib/objects.js index 190d401f..135005b6 100644 --- a/lib/objects.js +++ b/lib/objects.js @@ -24,9 +24,7 @@ function create(realmId, info) { object[keys.id] = info.id; object[keys.type] = info.type; - schema.properties.forEach((prop) => { - let name = prop.name; - + schema.properties.forEach((name) => { Object.defineProperty(object, name, { enumerable: true, get: util.getterForProperty(name), diff --git a/src/rpc.cpp b/src/rpc.cpp index ba7d5a7e..c7c024c5 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -289,12 +289,10 @@ json RPCServer::serialize_json_value(JSValueRef value) { } json RPCServer::serialize_object_schema(const realm::ObjectSchema &object_schema) { - json properties = json::array(); + std::vector properties; + for (realm::Property prop : object_schema.properties) { - properties.push_back({ - {"name", prop.name}, - {"type", RJSTypeGet(prop.type)}, - }); + properties.push_back(prop.name); } return { From 15052985f190713f221cab6e097b47b6a512b005 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 2 Nov 2015 22:16:50 -0800 Subject: [PATCH 08/15] Remove confusion between propTypes and objectTypes The RPC layer now only speaks in objectTypes, since they don't always equate to propTypes. We were overloading the use of propTypes for no good purpose. --- lib/constants.js | 7 ++++++- lib/lists.js | 6 ++++-- lib/realm.js | 4 ++-- lib/rpc.js | 18 ++++++++---------- src/js_util.cpp | 15 --------------- src/js_util.hpp | 2 -- src/rpc.cpp | 25 +++++++++++++++---------- 7 files changed, 35 insertions(+), 42 deletions(-) diff --git a/lib/constants.js b/lib/constants.js index ff538a0c..841dc5b8 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -17,13 +17,18 @@ let propTypes = {}; }); [ + 'DATA', + 'DATE', 'DICT', 'FUNCTION', + 'LIST', + 'OBJECT', 'REALM', 'RESULTS', + 'UNDEFINED', ].forEach(function(type) { Object.defineProperty(objectTypes, type, { - value: 'ObjectTypes' + type, + value: type.toLowerCase(), }); }); diff --git a/lib/lists.js b/lib/lists.js index 5232f843..3be63612 100644 --- a/lib/lists.js +++ b/lib/lists.js @@ -7,6 +7,8 @@ const constants = require('./constants'); const util = require('./util'); +const {objectTypes} = constants; + module.exports = { create, }; @@ -14,12 +16,12 @@ module.exports = { class List {} // Non-mutating methods: -util.createMethods(List.prototype, constants.propTypes.LIST, [ +util.createMethods(List.prototype, objectTypes.LIST, [ 'snapshot', ]); // Mutating methods: -util.createMethods(List.prototype, constants.propTypes.LIST, [ +util.createMethods(List.prototype, objectTypes.LIST, [ 'pop', 'shift', 'push', diff --git a/lib/realm.js b/lib/realm.js index aecc56de..608e0b08 100644 --- a/lib/realm.js +++ b/lib/realm.js @@ -14,8 +14,8 @@ const util = require('./util'); const {keys, propTypes, objectTypes} = constants; const listenersKey = Symbol(); -rpc.registerTypeConverter(propTypes.LIST, lists.create); -rpc.registerTypeConverter(propTypes.OBJECT, objects.create); +rpc.registerTypeConverter(objectTypes.LIST, lists.create); +rpc.registerTypeConverter(objectTypes.OBJECT, objects.create); rpc.registerTypeConverter(objectTypes.RESULTS, results.create); class Realm { diff --git a/lib/rpc.js b/lib/rpc.js index 43fb140b..00ab8d16 100644 --- a/lib/rpc.js +++ b/lib/rpc.js @@ -9,7 +9,7 @@ const constants = require('./constants'); const DEVICE_HOST = 'localhost:8082'; -const {keys, objectTypes, propTypes} = constants; +const {keys, objectTypes} = constants; const {id: idKey, realm: realmKey} = keys; const typeConverters = {}; @@ -39,8 +39,8 @@ module.exports = { clearTestState, }; -registerTypeConverter(propTypes.DATA, (_, {value}) => base64.decode(value)); -registerTypeConverter(propTypes.DATE, (_, {value}) => new Date(value)); +registerTypeConverter(objectTypes.DATA, (_, {value}) => base64.decode(value)); +registerTypeConverter(objectTypes.DATE, (_, {value}) => new Date(value)); registerTypeConverter(objectTypes.DICT, deserializeDict); function registerTypeConverter(type, handler) { @@ -96,14 +96,12 @@ function clearTestState() { } function serialize(realmId, value) { + if (typeof value == 'undefined') { + return {type: objectTypes.UNDEFINED}; + } if (typeof value == 'function') { return {type: objectTypes.FUNCTION}; } - - if (typeof value === 'undefined') { - return {type: 'undefined'}; - } - if (!value || typeof value != 'object') { return {value: value}; } @@ -118,7 +116,7 @@ function serialize(realmId, value) { } if (value instanceof Date) { - return {type: propTypes.DATE, value: value.getTime()}; + return {type: objectTypes.DATE, value: value.getTime()}; } if (Array.isArray(value)) { @@ -127,7 +125,7 @@ function serialize(realmId, value) { } if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) { - return {type: propTypes.DATA, value: base64.encode(value)}; + return {type: objectTypes.DATA, value: base64.encode(value)}; } let keys = Object.keys(value); diff --git a/src/js_util.cpp b/src/js_util.cpp index c9c9a96c..ebe71c63 100644 --- a/src/js_util.cpp +++ b/src/js_util.cpp @@ -23,21 +23,6 @@ JSValueRef RJSMakeError(JSContextRef ctx, const std::string &message) { return JSObjectMakeError(ctx, 1, &value, NULL); } -std::string RJSTypeGet(PropertyType propertyType) { - switch (propertyType) { - case PropertyTypeBool: return "bool"; - case PropertyTypeInt: return "int"; - case PropertyTypeFloat: return "float"; - case PropertyTypeDouble:return "double"; - case PropertyTypeString:return "string"; - case PropertyTypeDate: return "date"; - case PropertyTypeData: return "data"; - case PropertyTypeObject:return "object"; - case PropertyTypeArray: return "list"; - default: return nullptr; - } -} - std::string RJSStringForJSString(JSStringRef jsString) { std::string str; size_t maxSize = JSStringGetMaximumUTF8CStringSize(jsString); diff --git a/src/js_util.hpp b/src/js_util.hpp index c530e4e5..000dcf78 100644 --- a/src/js_util.hpp +++ b/src/js_util.hpp @@ -41,8 +41,6 @@ JSClassRef RJSCreateWrapperClass(const char * name, JSObjectGetPropertyCallback return JSClassCreate(&classDefinition); } -std::string RJSTypeGet(realm::PropertyType propertyType); - std::string RJSStringForJSString(JSStringRef jsString); std::string RJSStringForValue(JSContextRef ctx, JSValueRef value); std::string RJSValidatedStringForValue(JSContextRef ctx, JSValueRef value, const char * name = nullptr); diff --git a/src/rpc.cpp b/src/rpc.cpp index c7c024c5..db35c3df 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -22,9 +22,14 @@ using RJSAccessor = realm::NativeAccessor; using namespace realm_js; -static const char * const RealmObjectTypesDictionary = "ObjectTypesDICT"; -static const char * const RealmObjectTypesFunction = "ObjectTypesFUNCTION"; -static const char * const RealmObjectTypesResults = "ObjectTypesRESULTS"; +static const char * const RealmObjectTypesData = "data"; +static const char * const RealmObjectTypesDate = "date"; +static const char * const RealmObjectTypesDictionary = "dict"; +static const char * const RealmObjectTypesFunction = "function"; +static const char * const RealmObjectTypesList = "list"; +static const char * const RealmObjectTypesObject = "object"; +static const char * const RealmObjectTypesResults = "results"; +static const char * const RealmObjectTypesUndefined = "undefined"; RPCServer::RPCServer() { m_context = JSGlobalContextCreate(NULL); @@ -219,7 +224,7 @@ json RPCServer::serialize_json_value(JSValueRef value) { if (JSValueIsObjectOfClass(m_context, value, RJSObjectClass())) { realm::Object *object = RJSGetInternal(js_object); return { - {"type", RJSTypeGet(realm::PropertyTypeObject)}, + {"type", RealmObjectTypesObject}, {"id", store_object(js_object)}, {"schema", serialize_object_schema(object->get_object_schema())} }; @@ -227,7 +232,7 @@ json RPCServer::serialize_json_value(JSValueRef value) { else if (JSValueIsObjectOfClass(m_context, value, RJSListClass())) { realm::List *list = RJSGetInternal(js_object); return { - {"type", RJSTypeGet(realm::PropertyTypeArray)}, + {"type", RealmObjectTypesList}, {"id", store_object(js_object)}, {"size", list->size()}, {"schema", serialize_object_schema(list->get_object_schema())} @@ -253,13 +258,13 @@ json RPCServer::serialize_json_value(JSValueRef value) { else if (RJSIsValueArrayBuffer(m_context, value)) { std::string data = RJSAccessor::to_binary(m_context, value); return { - {"type", RJSTypeGet(realm::PropertyTypeData)}, + {"type", RealmObjectTypesData}, {"value", base64_encode((unsigned char *)data.data(), data.size())}, }; } else if (RJSIsValueDate(m_context, value)) { return { - {"type", RJSTypeGet(realm::PropertyTypeDate)}, + {"type", RealmObjectTypesDate}, {"value", RJSValidatedValueToNumber(m_context, value)}, }; } @@ -336,14 +341,14 @@ JSValueRef RPCServer::deserialize_json_value(const json dict) return js_object; } - else if (type_string == RJSTypeGet(realm::PropertyTypeData)) { + else if (type_string == RealmObjectTypesData) { std::string bytes; if (!base64_decode(value.get(), &bytes)) { throw std::runtime_error("Failed to decode base64 encoded data"); } return RJSAccessor::from_binary(m_context, realm::BinaryData(bytes)); } - else if (type_string == RJSTypeGet(realm::PropertyTypeDate)) { + else if (type_string == RealmObjectTypesDate) { JSValueRef exception = NULL; JSValueRef time = JSValueMakeNumber(m_context, value.get()); JSObjectRef date = JSObjectMakeDate(m_context, 1, &time, &exception); @@ -353,7 +358,7 @@ JSValueRef RPCServer::deserialize_json_value(const json dict) } return date; } - else if (type_string == "undefined") { + else if (type_string == RealmObjectTypesUndefined) { return JSValueMakeUndefined(m_context); } assert(0); From 09752846e59075c3c0204b123596e12b970592ac Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 2 Nov 2015 23:05:06 -0800 Subject: [PATCH 09/15] Update example apps with new schema API --- examples/ReactExample/components/realm.js | 16 ++++++++-------- examples/ReactExample/components/todo-app.js | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/ReactExample/components/realm.js b/examples/ReactExample/components/realm.js index 7ab84d75..d3f44bed 100644 --- a/examples/ReactExample/components/realm.js +++ b/examples/ReactExample/components/realm.js @@ -10,17 +10,17 @@ module.exports = new Realm({ schema: [ { name: 'Todo', - properties: [ - {name: 'done', type: Realm.Types.BOOL, default: false}, - {name: 'text', type: Realm.Types.STRING, default: ''}, - ] + properties: { + done: {type: Realm.Types.BOOL, default: false}, + text: Realm.Types.STRING, + }, }, { name: 'TodoList', - properties: [ - {name: 'name', type: Realm.Types.STRING}, - {name: 'items', type: Realm.Types.LIST, objectType: 'Todo'}, - ] + properties: { + name: Realm.Types.STRING, + items: {type: Realm.Types.LIST, objectType: 'Todo', default: []}, + }, }, ], }); diff --git a/examples/ReactExample/components/todo-app.js b/examples/ReactExample/components/todo-app.js index fc7174cb..eb4f6ee1 100644 --- a/examples/ReactExample/components/todo-app.js +++ b/examples/ReactExample/components/todo-app.js @@ -19,7 +19,7 @@ class TodoApp extends React.Component { let todoLists = realm.objects('TodoList'); if (todoLists.length < 1) { realm.write(() => { - realm.create('TodoList', {name: 'Todo List', items: []}); + realm.create('TodoList', {name: 'Todo List'}); }); } @@ -78,7 +78,7 @@ class TodoApp extends React.Component { } realm.write(() => { - realm.create('TodoList', {name: '', items: []}); + realm.create('TodoList', {name: ''}); }); this._setEditingRow(items.length - 1); From 9be1331dbff66022688b4e0611bffd71f4a7461b Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 2 Nov 2015 12:02:53 -0800 Subject: [PATCH 10/15] Convert tests to create objects with property objects This makes it more readable and will be the preferred syntax in the documentation (and is much less likely to be deprecated in the future!). --- tests/lib/list-tests.js | 146 +++++++++++++++++++++++++++---------- tests/lib/object-tests.js | 144 ++++++++++++++++++++++++++---------- tests/lib/realm-tests.js | 131 ++++++++++++++++++++++++--------- tests/lib/results-tests.js | 30 ++++---- tests/lib/schemas.js | 4 +- 5 files changed, 330 insertions(+), 125 deletions(-) diff --git a/tests/lib/list-tests.js b/tests/lib/list-tests.js index ddd629e9..0bea2ce0 100644 --- a/tests/lib/list-tests.js +++ b/tests/lib/list-tests.js @@ -12,20 +12,30 @@ var schemas = require('./schemas'); module.exports = BaseTest.extend({ testArrayLength: function() { var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]}); + var array; + realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3]]]); - TestCase.assertEqual(obj.arrayCol.length, 1); + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}], + }); + + array = obj.arrayCol; + TestCase.assertEqual(array.length, 1); obj.arrayCol = []; - TestCase.assertEqual(obj.arrayCol.length, 0); + TestCase.assertEqual(array.length, 0); - obj.arrayCol = [[1], [2]]; - TestCase.assertEqual(obj.arrayCol.length, 2); + obj.arrayCol = [{doubleCol: 1}, {doubleCol: 2}]; + TestCase.assertEqual(array.length, 2); TestCase.assertThrows(function() { - obj.arrayCol.length = 0; + array.length = 0; }, 'cannot set length property on lists'); }); + + TestCase.assertEqual(array.length, 2); }, testArraySubscriptGetters: function() { @@ -33,7 +43,12 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]); + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}, {doubleCol: 4}], + }); + array = obj.arrayCol; }); @@ -48,26 +63,36 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]); - array = obj.arrayCol; + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}, {doubleCol: 4}], + }); - array[0] = [5]; - array[1] = [6]; + array = obj.arrayCol; + array[0] = {doubleCol: 5}; + array[1] = {doubleCol: 6}; TestCase.assertEqual(array[0].doubleCol, 5); TestCase.assertEqual(array[1].doubleCol, 6); + array[0] = obj.objectCol; + array[1] = obj.objectCol1; + + TestCase.assertEqual(array[0].doubleCol, 1); + TestCase.assertEqual(array[1].doubleCol, 2); + TestCase.assertThrows(function() { - array[2] = [1]; + array[2] = {doubleCol: 1}; }, 'cannot set list item beyond its bounds'); TestCase.assertThrows(function() { - array[-1] = [1]; + array[-1] = {doubleCol: 1}; }, 'cannot set list item with negative index'); }); TestCase.assertThrows(function() { - array[0] = [3]; + array[0] = {doubleCol: 1}; }, 'cannot set list item outside write transaction'); }, @@ -76,7 +101,12 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]); + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}, {doubleCol: 4}], + }); + array = obj.arrayCol; }); @@ -88,21 +118,26 @@ module.exports = BaseTest.extend({ var obj; realm.write(function() { - obj = realm.create('LinkTypesObject', [[1], [2], []]); + obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [], + }); }); - for (var index in obj.arrayCol) { + var index; + for (index in obj.arrayCol) { TestCase.assertTrue(false, "No objects should have been enumerated: " + index); } realm.write(function() { - obj.arrayCol = [[0], [1]]; + obj.arrayCol = [{doubleCol: 0}, {doubleCol: 1}]; TestCase.assertEqual(obj.arrayCol.length, 2); }); var count = 0; var keys = Object.keys(obj.arrayCol); - for (var index in obj.arrayCol) { + for (index in obj.arrayCol) { TestCase.assertEqual(count++, +index); TestCase.assertEqual(keys[index], index); } @@ -116,11 +151,16 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3]]]); - TestCase.assertEqual(obj.arrayCol.length, 1); + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}], + }); array = obj.arrayCol; - TestCase.assertEqual(array.push([4]), 2); + TestCase.assertEqual(array.length, 1); + + TestCase.assertEqual(array.push({doubleCol: 4}), 2); TestCase.assertEqual(array.length, 2); TestCase.assertEqual(array[1].doubleCol, 4); @@ -145,9 +185,13 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]); - array = obj.arrayCol; + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}, {doubleCol: 4}], + }); + array = obj.arrayCol; TestCase.assertEqual(array.pop().doubleCol, 4); TestCase.assertEqual(array.pop().doubleCol, 3); TestCase.assertEqual(array.length, 0); @@ -169,11 +213,16 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3]]]); - TestCase.assertEqual(obj.arrayCol.length, 1); + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}], + }); array = obj.arrayCol; - TestCase.assertEqual(array.unshift([5]), 2); + TestCase.assertEqual(array.length, 1); + + TestCase.assertEqual(array.unshift({doubleCol: 5}), 2); TestCase.assertEqual(array.length, 2); TestCase.assertEqual(array[0].doubleCol, 5); @@ -185,7 +234,7 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(array.length, 4); TestCase.assertThrows(function() { - array.unshift([1]); + array.unshift({doubleCol: 1}); }, 'can only unshift in a write transaction'); }, @@ -194,9 +243,13 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]); - array = obj.arrayCol; + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}, {doubleCol: 4}], + }); + array = obj.arrayCol; TestCase.assertEqual(array.shift().doubleCol, 3); TestCase.assertEqual(array.shift().doubleCol, 4); TestCase.assertEqual(array.length, 0); @@ -218,10 +271,14 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]); - var removed; + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}, {doubleCol: 4}], + }); array = obj.arrayCol; + var removed; removed = array.splice(0, 0, obj.objectCol, obj.objectCol1); TestCase.assertEqual(removed.length, 0); @@ -229,7 +286,7 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(array[0].doubleCol, 1); TestCase.assertEqual(array[1].doubleCol, 2); - removed = array.splice(2, 2, [5], [6]); + removed = array.splice(2, 2, {doubleCol: 5}, {doubleCol: 6}); TestCase.assertEqual(removed.length, 2); TestCase.assertEqual(removed[0].doubleCol, 3); TestCase.assertEqual(removed[1].doubleCol, 4); @@ -270,7 +327,7 @@ module.exports = BaseTest.extend({ }); TestCase.assertThrows(function() { - array.splice(0, 0, [1]); + array.splice(0, 0, {doubleCol: 1}); }, 'can only splice in a write transaction'); }, @@ -280,7 +337,12 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - object = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]); + object = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}, {doubleCol: 4}], + }); + array = object.arrayCol; }); @@ -313,21 +375,27 @@ module.exports = BaseTest.extend({ var array; realm.write(function() { - var obj = realm.create('LinkTypesObject', [[1], [2], [[3], [4]]]); + var obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: {doubleCol: 2}, + arrayCol: [{doubleCol: 3}, {doubleCol: 4}], + }); + array = obj.arrayCol; }); + TestCase.assertEqual(array.length, 2); TestCase.assertEqual(objects.length, 4); try { realm.write(function() { - array.push([5]); + array.push({doubleCol: 5}); TestCase.assertEqual(objects.length, 5); - array.unshift([2]); + array.unshift({doubleCol: 2}); TestCase.assertEqual(objects.length, 6); - array.splice(0, 0, [1]); + array.splice(0, 0, {doubleCol: 1}); TestCase.assertEqual(objects.length, 7); array.push(objects[0], objects[1]); diff --git a/tests/lib/object-tests.js b/tests/lib/object-tests.js index 4bdb49df..c2cf03c2 100644 --- a/tests/lib/object-tests.js +++ b/tests/lib/object-tests.js @@ -16,67 +16,105 @@ var RANDOM_DATA = new Uint8Array([ module.exports = BaseTest.extend({ testBasicTypesPropertyGetters: function() { - var basicTypesValues = [true, 1, 1.1, 1.11, 'string', new Date(1), RANDOM_DATA]; var realm = new Realm({schema: [schemas.BasicTypes]}); var object; + var basicTypesValues = { + boolCol: true, + intCol: 1, + floatCol: 1.1, + doubleCol: 1.11, + stringCol: 'string', + dateCol: new Date(1), + dataCol: RANDOM_DATA, + }; + realm.write(function() { object = realm.create('BasicTypesObject', basicTypesValues); }); - for (var i = 0; i < schemas.BasicTypes.properties.length; i++) { - var prop = schemas.BasicTypes.properties[i]; - if (prop.type == Realm.Types.FLOAT) { - TestCase.assertEqualWithTolerance(object[prop.name], basicTypesValues[i], 0.000001); + for (var name in schemas.BasicTypes.properties) { + var prop = schemas.BasicTypes.properties[name]; + var type = typeof prop == 'object' ? prop.type : prop; + + if (type == Realm.Types.FLOAT || type == Realm.Types.DOUBLE) { + TestCase.assertEqualWithTolerance(object[name], basicTypesValues[name], 0.000001); } - else if (prop.type == Realm.Types.DATA) { - TestCase.assertArraysEqual(new Uint8Array(object[prop.name]), RANDOM_DATA); + else if (type == Realm.Types.DATA) { + TestCase.assertArraysEqual(new Uint8Array(object[name]), RANDOM_DATA); } - else if (prop.type == Realm.Types.DATE) { - TestCase.assertEqual(object[prop.name].getTime(), basicTypesValues[i].getTime()); + else if (type == Realm.Types.DATE) { + TestCase.assertEqual(object[name].getTime(), basicTypesValues[name].getTime()); } else { - TestCase.assertEqual(object[prop.name], basicTypesValues[i]); + TestCase.assertEqual(object[name], basicTypesValues[name]); } } TestCase.assertEqual(object.nonexistent, undefined); }, testNullableBasicTypesPropertyGetters: function() { - var nullValues = [null, null, null, null, null, null, null]; - var basicTypesValues = [true, 1, 1.1, 1.11, 'string', new Date(1), RANDOM_DATA]; - var realm = new Realm({schema: [schemas.NullableBasicTypes]}); - var nullObject = null; - var object = null; + var object, nullObject; + + var basicTypesValues = { + boolCol: true, + intCol: 1, + floatCol: 1.1, + doubleCol: 1.11, + stringCol: 'string', + dateCol: new Date(1), + dataCol: RANDOM_DATA, + }; + realm.write(function() { - nullObject = realm.create('NullableBasicTypesObject', nullValues); object = realm.create('NullableBasicTypesObject', basicTypesValues); + + nullObject = realm.create('NullableBasicTypesObject', { + boolCol: null, + intCol: null, + floatCol: null, + doubleCol: null, + stringCol: null, + dateCol: null, + dataCol: null, + }); }); - for (var i = 0; i < schemas.BasicTypes.properties.length; i++) { - var prop = schemas.BasicTypes.properties[i]; - TestCase.assertEqual(nullObject[prop.name], null); + for (var name in schemas.BasicTypes.properties) { + var prop = schemas.BasicTypes.properties[name]; + var type = typeof prop == 'object' ? prop.type : prop; - if (prop.type == Realm.Types.FLOAT) { - TestCase.assertEqualWithTolerance(object[prop.name], basicTypesValues[i], 0.000001); + TestCase.assertEqual(nullObject[name], null); + + if (type == Realm.Types.FLOAT || type == Realm.Types.DOUBLE) { + TestCase.assertEqualWithTolerance(object[name], basicTypesValues[name], 0.000001); } - else if (prop.type == Realm.Types.DATA) { - TestCase.assertArraysEqual(new Uint8Array(object[prop.name]), RANDOM_DATA); + else if (type == Realm.Types.DATA) { + TestCase.assertArraysEqual(new Uint8Array(object[name]), RANDOM_DATA); } - else if (prop.type == Realm.Types.DATE) { - TestCase.assertEqual(object[prop.name].getTime(), basicTypesValues[i].getTime()); + else if (type == Realm.Types.DATE) { + TestCase.assertEqual(object[name].getTime(), basicTypesValues[name].getTime()); } else { - TestCase.assertEqual(object[prop.name], basicTypesValues[i]); + TestCase.assertEqual(object[name], basicTypesValues[name]); } } }, testBasicTypesPropertySetters: function() { - var basicTypesValues = [true, 1, 1.1, 1.11, 'string', new Date(1), new ArrayBuffer()]; var realm = new Realm({schema: [schemas.BasicTypes]}); - var obj = null; + var obj; + + var basicTypesValues = { + boolCol: true, + intCol: 1, + floatCol: 1.1, + doubleCol: 1.11, + stringCol: 'string', + dateCol: new Date(1), + dataCol: new ArrayBuffer(), + }; realm.write(function() { obj = realm.create('BasicTypesObject', basicTypesValues); @@ -92,7 +130,7 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(obj.boolCol, false, 'wrong bool value'); TestCase.assertEqual(obj.intCol, 2, 'wrong int value'); TestCase.assertEqualWithTolerance(obj.floatCol, 2.2, 0.000001, 'wrong float value'); - TestCase.assertEqual(obj.doubleCol, 2.22, 'wrong double value'); + TestCase.assertEqualWithTolerance(obj.doubleCol, 2.22, 0.000001, 'wrong double value'); TestCase.assertEqual(obj.stringCol, 'STRING', 'wrong string value'); TestCase.assertEqual(obj.dateCol.getTime(), 2, 'wrong date value'); TestCase.assertArraysEqual(new Uint8Array(obj.dataCol), RANDOM_DATA, 'wrong data value'); @@ -156,13 +194,23 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(obj.boolCol, false, 'bool value changed outside transaction'); }, testNullableBasicTypesPropertySetters: function() { - var basicTypesValues = [true, 1, 1.1, 1.11, 'string', new Date(1), RANDOM_DATA]; var realm = new Realm({schema: [schemas.NullableBasicTypes]}); var obj, obj1; + var basicTypesValues = { + boolCol: true, + intCol: 1, + floatCol: 1.1, + doubleCol: 1.11, + stringCol: 'string', + dateCol: new Date(1), + dataCol: RANDOM_DATA, + }; + realm.write(function() { obj = realm.create('NullableBasicTypesObject', basicTypesValues); obj1 = realm.create('NullableBasicTypesObject', basicTypesValues); + for (var name in schemas.NullableBasicTypes.properties) { obj[name] = null; obj1[name] = undefined; @@ -190,8 +238,13 @@ module.exports = BaseTest.extend({ testLinkTypesPropertyGetters: function() { var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]}); var obj = null; + realm.write(function() { - obj = realm.create('LinkTypesObject', [[1], null, [[3]]]); + obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: null, + arrayCol: [{doubleCol: 3}], + }); }); var objVal = obj.objectCol; @@ -210,11 +263,16 @@ module.exports = BaseTest.extend({ testLinkTypesPropertySetters: function() { var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]}); var objects = realm.objects('TestObject'); - var obj = null; + var obj; realm.write(function() { - obj = realm.create('LinkTypesObject', [[1], null, [[3]]]); + obj = realm.create('LinkTypesObject', { + objectCol: {doubleCol: 1}, + objectCol1: null, + arrayCol: [{doubleCol: 3}], + }); }); + TestCase.assertEqual(objects.length, 2); TestCase.assertThrows(function() { @@ -238,15 +296,20 @@ module.exports = BaseTest.extend({ // set object as JSON realm.write(function() { - obj.objectCol = { doubleCol: 1 }; + obj.objectCol = {doubleCol: 1}; }); TestCase.assertEqual(obj.objectCol.doubleCol, 1); TestCase.assertEqual(objects.length, 3); // set array property realm.write(function() { - obj.arrayCol = [obj.arrayCol[0], obj.objectCol, realm.create('TestObject', [2])]; + obj.arrayCol = [ + obj.arrayCol[0], + obj.objectCol, + realm.create('TestObject', {doubleCol: 2}), + ]; }); + TestCase.assertEqual(objects.length, 4); TestCase.assertEqual(obj.arrayCol.length, 3); TestCase.assertEqual(obj.arrayCol[0].doubleCol, 3); @@ -254,12 +317,19 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(obj.arrayCol[2].doubleCol, 2); }, testEnumerablePropertyNames: function() { - var basicTypesValues = [true, 1, 1.1, 1.11, 'string', new Date(1), new ArrayBuffer()]; var realm = new Realm({schema: [schemas.BasicTypes]}); var object; realm.write(function() { - object = realm.create('BasicTypesObject', basicTypesValues); + object = realm.create('BasicTypesObject', { + boolCol: true, + intCol: 1, + floatCol: 1.1, + doubleCol: 1.11, + stringCol: 'string', + dateCol: new Date(1), + dataCol: RANDOM_DATA, + }); }); var propNames = Object.keys(schemas.BasicTypes.properties); diff --git a/tests/lib/realm-tests.js b/tests/lib/realm-tests.js index 3290b15a..8bde87cd 100644 --- a/tests/lib/realm-tests.js +++ b/tests/lib/realm-tests.js @@ -12,8 +12,12 @@ var util = require('./util'); module.exports = BaseTest.extend({ testRealmConstructorPath: function() { - TestCase.assertThrows(function() { new Realm('/invalidpath'); }); - TestCase.assertThrows(function() { new Realm(util.realmPathForFile('test1.realm'), 'invalidArgument'); }); + TestCase.assertThrows(function() { + new Realm('/invalidpath'); + }, 'Realm cannot be created with an invalid path'); + TestCase.assertThrows(function() { + new Realm(util.realmPathForFile('test1.realm'), 'invalidArgument'); + }, 'Realm constructor can only have 0 or 1 argument(s)'); var defaultRealm = new Realm({schema: []}); TestCase.assertEqual(defaultRealm.path, Realm.defaultPath); @@ -57,7 +61,7 @@ module.exports = BaseTest.extend({ realm = new Realm({path: testPath, schema: [schemas.TestObject], schemaVersion: 2}); realm.write(function() { - realm.create('TestObject', [1]); + realm.create('TestObject', {doubleCol: 1}); }); TestCase.assertEqual(realm.objects('TestObject')[0].doubleCol, 1) }, @@ -78,12 +82,12 @@ module.exports = BaseTest.extend({ var realm = new Realm({schema: [schemas.IntPrimary, schemas.AllTypes, schemas.TestObject]}); TestCase.assertThrows(function() { - realm.create('TestObject', [1]); + realm.create('TestObject', {doubleCol: 1}); }, 'can only create inside a write transaction'); realm.write(function() { - realm.create('TestObject', [1]); - realm.create('TestObject', {'doubleCol': 2}); + realm.create('TestObject', {doubleCol: 1}); + realm.create('TestObject', {doubleCol: 2}); }); var objects = realm.objects('TestObject'); @@ -93,16 +97,31 @@ module.exports = BaseTest.extend({ // test int primary object realm.write(function() { - var obj0 = realm.create('IntPrimaryObject', [0, 'val0']); + var obj0 = realm.create('IntPrimaryObject', { + primaryCol: 0, + valueCol: 'val0', + }); TestCase.assertThrows(function() { - realm.create('IntPrimaryObject', [0, 'val0']); - }); - realm.create('IntPrimaryObject', [1, 'val1'], true); + realm.create('IntPrimaryObject', { + primaryCol: 0, + valueCol: 'val0', + }); + }, 'cannot create object with conflicting primary key'); + + realm.create('IntPrimaryObject', { + primaryCol: 1, + valueCol: 'val1', + }, true); + var objects = realm.objects('IntPrimaryObject'); TestCase.assertEqual(objects.length, 2); - realm.create('IntPrimaryObject', [0, 'newVal0'], true); + realm.create('IntPrimaryObject', { + primaryCol: 0, + valueCol: 'newVal0', + }, true); + TestCase.assertEqual(obj0.valueCol, 'newVal0'); TestCase.assertEqual(objects.length, 2); @@ -112,47 +131,89 @@ module.exports = BaseTest.extend({ // test upsert with all type and string primary object realm.write(function() { - var values = ['0', true, 1, 1.1, 1.11, '1', new Date(1), new ArrayBuffer(1), [1], []]; + var values = { + primaryCol: '0', + boolCol: true, + intCol: 1, + floatCol: 1.1, + doubleCol: 1.11, + stringCol: '1', + dateCol: new Date(1), + dataCol: new ArrayBuffer(1), + objectCol: {doubleCol: 1}, + arrayCol: [], + }; + var obj0 = realm.create('AllTypesObject', values); TestCase.assertThrows(function() { realm.create('AllTypesObject', values); - }); - var obj1 = realm.create('AllTypesObject', ['1', false, 2, 2.2, 2.11, '2', new Date(2), new ArrayBuffer(2), [0], [[2]]], true); + }, 'cannot create object with conflicting primary key'); + + var obj1 = realm.create('AllTypesObject', { + primaryCol: '1', + boolCol: false, + intCol: 2, + floatCol: 2.2, + doubleCol: 2.22, + stringCol: '2', + dateCol: new Date(2), + dataCol: new ArrayBuffer(2), + objectCol: {doubleCol: 0}, + arrayCol: [{doubleCol: 2}], + }, true); + var objects = realm.objects('AllTypesObject'); TestCase.assertEqual(objects.length, 2); - realm.create('AllTypesObject', ['0', false, 2, 2.2, 2.22, '2', new Date(2), new ArrayBuffer(2), null, [[2]]], true); + realm.create('AllTypesObject', { + primaryCol: '0', + boolCol: false, + intCol: 2, + floatCol: 2.2, + doubleCol: 2.22, + stringCol: '2', + dateCol: new Date(2), + dataCol: new ArrayBuffer(2), + objectCol: null, + arrayCol: [{doubleCol: 2}], + }, true); + TestCase.assertEqual(objects.length, 2); TestCase.assertEqual(obj0.stringCol, '2'); TestCase.assertEqual(obj0.boolCol, false); TestCase.assertEqual(obj0.intCol, 2); TestCase.assertEqualWithTolerance(obj0.floatCol, 2.2, 0.000001); - TestCase.assertEqual(obj0.doubleCol, 2.22); + TestCase.assertEqualWithTolerance(obj0.doubleCol, 2.22, 0.000001); TestCase.assertEqual(obj0.dateCol.getTime(), 2); TestCase.assertEqual(obj0.dataCol.byteLength, 2); TestCase.assertEqual(obj0.objectCol, null); TestCase.assertEqual(obj0.arrayCol.length, 1); - realm.create('AllTypesObject', { primaryCol: '0' }, true); - realm.create('AllTypesObject', { primaryCol: '1' }, true); + realm.create('AllTypesObject', {primaryCol: '0'}, true); + realm.create('AllTypesObject', {primaryCol: '1'}, true); TestCase.assertEqual(obj0.stringCol, '2'); TestCase.assertEqual(obj0.objectCol, null); TestCase.assertEqual(obj1.objectCol.doubleCol, 0); - realm.create('AllTypesObject', { primaryCol: '0', stringCol: '3', objectCol: [0] }, true); + realm.create('AllTypesObject', { + primaryCol: '0', + stringCol: '3', + objectCol: {doubleCol: 0}, + }, true); + TestCase.assertEqual(obj0.stringCol, '3'); TestCase.assertEqual(obj0.boolCol, false); TestCase.assertEqual(obj0.intCol, 2); TestCase.assertEqualWithTolerance(obj0.floatCol, 2.2, 0.000001); - TestCase.assertEqual(obj0.doubleCol, 2.22); + TestCase.assertEqualWithTolerance(obj0.doubleCol, 2.22, 0.000001); TestCase.assertEqual(obj0.dateCol.getTime(), 2); TestCase.assertEqual(obj0.dataCol.byteLength, 2); TestCase.assertEqual(obj0.objectCol.doubleCol, 0); TestCase.assertEqual(obj0.arrayCol.length, 1); - realm.create('AllTypesObject', { primaryCol: '0', objectCol: undefined }, true); - realm.create('AllTypesObject', { primaryCol: '1', objectCol: null }, true); + realm.create('AllTypesObject', {primaryCol: '0', objectCol: undefined}, true); + realm.create('AllTypesObject', {primaryCol: '1', objectCol: null}, true); TestCase.assertEqual(obj0.objectCol, null); TestCase.assertEqual(obj1.objectCol, null); }); @@ -160,6 +221,7 @@ module.exports = BaseTest.extend({ testRealmCreateWithDefaults: function() { var realm = new Realm({schema: [schemas.DefaultValues, schemas.TestObject]}); + realm.write(function() { var obj = realm.create('DefaultValuesObject', {}); var properties = schemas.DefaultValues.properties; @@ -167,22 +229,23 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(obj.boolCol, properties.boolCol.default); TestCase.assertEqual(obj.intCol, properties.intCol.default); TestCase.assertEqualWithTolerance(obj.floatCol, properties.floatCol.default, 0.000001); - TestCase.assertEqual(obj.doubleCol, properties.doubleCol.default); + TestCase.assertEqualWithTolerance(obj.doubleCol, properties.doubleCol.default, 0.000001); TestCase.assertEqual(obj.stringCol, properties.stringCol.default); TestCase.assertEqual(obj.dateCol.getTime(), properties.dateCol.default.getTime()); TestCase.assertEqual(obj.dataCol.byteLength, properties.dataCol.default.byteLength); - TestCase.assertEqual(obj.objectCol.doubleCol, properties.objectCol.default[0]); + TestCase.assertEqual(obj.objectCol.doubleCol, properties.objectCol.default.doubleCol); TestCase.assertEqual(obj.nullObjectCol, null); TestCase.assertEqual(obj.arrayCol.length, properties.arrayCol.default.length); - TestCase.assertEqual(obj.arrayCol[0].doubleCol, properties.arrayCol.default[0][0]); + TestCase.assertEqual(obj.arrayCol[0].doubleCol, properties.arrayCol.default[0].doubleCol); }); }, testRealmDelete: function() { var realm = new Realm({schema: [schemas.TestObject]}); + realm.write(function() { for (var i = 0; i < 10; i++) { - realm.create('TestObject', [i]); + realm.create('TestObject', {doubleCol: i}); } }); @@ -216,11 +279,13 @@ module.exports = BaseTest.extend({ testDeleteAll: function() { var realm = new Realm({schema: [schemas.TestObject, schemas.IntPrimary]}); + realm.write(function() { - realm.create('TestObject', [1]); - realm.create('TestObject', [2]); - realm.create('IntPrimaryObject', [2, 'value']); + realm.create('TestObject', {doubleCol: 1}); + realm.create('TestObject', {doubleCol: 2}); + realm.create('IntPrimaryObject', {primaryCol: 2, valueCol: 'value'}); }); + TestCase.assertEqual(realm.objects('TestObject').length, 2); TestCase.assertEqual(realm.objects('IntPrimaryObject').length, 1); @@ -239,10 +304,10 @@ module.exports = BaseTest.extend({ testRealmObjects: function() { var realm = new Realm({schema: [schemas.PersonObject, schemas.DefaultValues, schemas.TestObject]}); realm.write(function() { - realm.create('PersonObject', ['Ari', 10, false]); - realm.create('PersonObject', ['Tim', 11, false]); - realm.create('PersonObject', {'name': 'Bjarne', 'age': 12}); - realm.create('PersonObject', {'name': 'Alex', 'age': 12, 'married': true}); + realm.create('PersonObject', {name: 'Ari', age: 10}); + realm.create('PersonObject', {name: 'Tim', age: 11}); + realm.create('PersonObject', {name: 'Bjarne', age: 12}); + realm.create('PersonObject', {name: 'Alex', age: 12, married: true}); }); TestCase.assertThrows(function() { diff --git a/tests/lib/results-tests.js b/tests/lib/results-tests.js index f26f7633..26d088a5 100644 --- a/tests/lib/results-tests.js +++ b/tests/lib/results-tests.js @@ -16,7 +16,7 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(objects.length, 0); realm.write(function() { - realm.create('TestObject', [1]); + realm.create('TestObject', {doubleCol: 1}); TestCase.assertEqual(objects.length, 1); }); TestCase.assertEqual(objects.length, 1); @@ -24,8 +24,8 @@ module.exports = BaseTest.extend({ testResultsSubscript: function() { var realm = new Realm({schema: [schemas.PersonObject]}); realm.write(function() { - realm.create('PersonObject', ['name1', 1, false]); - realm.create('PersonObject', ['name2', 2, false]); + realm.create('PersonObject', {name: 'name1', age: 1}); + realm.create('PersonObject', {name: 'name2', age: 2}); }); var people = realm.objects('PersonObject'); @@ -41,17 +41,17 @@ module.exports = BaseTest.extend({ var objects = realm.objects('TestObject'); realm.write(function() { - realm.create('TestObject', [1]); + realm.create('TestObject', {doubleCol: 1}); }); TestCase.assertThrows(function() { - objects[-1] = [0]; + objects[-1] = {doubleCol: 0}; }); TestCase.assertThrows(function() { - objects[0] = [0]; + objects[0] = {doubleCol: 0}; }); TestCase.assertThrows(function() { - objects[1] = [0]; + objects[1] = {doubleCol: 0}; }); TestCase.assertThrows(function() { objects.length = 0; @@ -71,12 +71,13 @@ module.exports = BaseTest.extend({ testResultsEnumerate: function() { var realm = new Realm({schema: [schemas.TestObject]}); var objects = realm.objects('TestObject'); - for (var object in objects) { + + for (var index in objects) { TestCase.assertTrue(false, "No objects should have been enumerated"); } realm.write(function() { - realm.create('TestObject', [1]); + realm.create('TestObject', {doubleCol: 1}); TestCase.assertEqual(objects.length, 1); }); @@ -93,12 +94,13 @@ module.exports = BaseTest.extend({ testSort: function() { var realm = new Realm({schema: [schemas.TestObject]}); var objects = realm.objects('TestObject'); + realm.write(function() { - realm.create('TestObject', [2]); - realm.create('TestObject', [3]); - realm.create('TestObject', [1]); - realm.create('TestObject', [4]); - realm.create('TestObject', [0]); + realm.create('TestObject', {doubleCol: 2}); + realm.create('TestObject', {doubleCol: 3}); + realm.create('TestObject', {doubleCol: 1}); + realm.create('TestObject', {doubleCol: 4}); + realm.create('TestObject', {doubleCol: 0}); }); objects.sortByProperty('doubleCol'); diff --git a/tests/lib/schemas.js b/tests/lib/schemas.js index 86ca9eba..d4bac072 100644 --- a/tests/lib/schemas.js +++ b/tests/lib/schemas.js @@ -98,9 +98,9 @@ exports.DefaultValues = { stringCol: {type: Realm.Types.STRING, default: 'defaultString'}, dateCol: {type: Realm.Types.DATE, default: new Date(1.111)}, dataCol: {type: Realm.Types.DATA, default: new ArrayBuffer(1)}, - objectCol: {type: 'TestObject', default: [1]}, + objectCol: {type: 'TestObject', default: {doubleCol: 1}}, nullObjectCol: {type: 'TestObject', default: null}, - arrayCol: {type: Realm.Types.LIST, objectType: 'TestObject', default: [[2]]}, + arrayCol: {type: Realm.Types.LIST, objectType: 'TestObject', default: [{doubleCol: 2}]}, } }; From 676849a3388fbb72348fcd9e6011df9d71328cd2 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 4 Jan 2016 14:51:35 -0800 Subject: [PATCH 11/15] Cleanup some code and comments to make it consistent --- src/js_schema.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/js_schema.cpp b/src/js_schema.cpp index 9873f991..757523fd 100644 --- a/src/js_schema.cpp +++ b/src/js_schema.cpp @@ -115,8 +115,12 @@ static inline Property RJSParseProperty(JSContextRef ctx, JSValueRef propertyAtt } static inline ObjectSchema RJSParseObjectSchema(JSContextRef ctx, JSObjectRef objectSchemaObject, std::map &defaults, std::map &prototypes) { - static JSStringRef schemaString = JSStringCreateWithUTF8CString("schema"); + static JSStringRef nameString = JSStringCreateWithUTF8CString("name"); + static JSStringRef primaryString = JSStringCreateWithUTF8CString("primaryKey"); static JSStringRef prototypeString = JSStringCreateWithUTF8CString("prototype"); + static JSStringRef propertiesString = JSStringCreateWithUTF8CString("properties"); + static JSStringRef schemaString = JSStringCreateWithUTF8CString("schema"); + JSObjectRef prototypeObject = NULL; JSValueRef prototypeValue = RJSValidatedPropertyValue(ctx, objectSchemaObject, prototypeString); @@ -131,14 +135,11 @@ static inline ObjectSchema RJSParseObjectSchema(JSContextRef ctx, JSObjectRef ob } } - static JSStringRef nameString = JSStringCreateWithUTF8CString("name"); - static JSStringRef propertiesString = JSStringCreateWithUTF8CString("properties"); - JSObjectRef propertiesObject = RJSValidatedObjectProperty(ctx, objectSchemaObject, propertiesString, "ObjectSchema must have a 'properties' object."); - ObjectDefaults objectDefaults; ObjectSchema objectSchema; objectSchema.name = RJSValidatedStringProperty(ctx, objectSchemaObject, nameString); + JSObjectRef propertiesObject = RJSValidatedObjectProperty(ctx, objectSchemaObject, propertiesString, "ObjectSchema must have a 'properties' object."); JSPropertyNameArrayRef propertyNames = NULL; size_t propertyCount; @@ -172,7 +173,6 @@ static inline ObjectSchema RJSParseObjectSchema(JSContextRef ctx, JSObjectRef ob JSPropertyNameArrayRelease(propertyNames); } - static JSStringRef primaryString = JSStringCreateWithUTF8CString("primaryKey"); JSValueRef primaryValue = RJSValidatedPropertyValue(ctx, objectSchemaObject, primaryString); if (!JSValueIsUndefined(ctx, primaryValue)) { objectSchema.primary_key = RJSValidatedStringForValue(ctx, primaryValue); @@ -183,7 +183,7 @@ static inline ObjectSchema RJSParseObjectSchema(JSContextRef ctx, JSObjectRef ob property->is_primary = true; } - // store prototype + // Store prototype so that objects of this type will have their prototype set to this prototype object. if (prototypeObject) { JSValueProtect(ctx, prototypeObject); prototypes[objectSchema.name] = std::move(prototypeObject); From 2a7c336ba922ffba0e368bd0c2b9a288e991b1d4 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 4 Jan 2016 15:58:16 -0800 Subject: [PATCH 12/15] Update README with new Schema API --- README.md | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index f277e2b1..d05003f7 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,12 @@ const Realm = require('realm'); const personSchema = { name: 'Person', primaryKey: 'name', - properties: [ - {name: 'name', type: Realm.Types.STRING}, - {name: 'birthday', type: Realm.Types.DATE}, - {name: 'friends', type: Realm.Types.LIST, objectType: 'Person'}, - {name: 'points', type: Realm.Types.INT, default: 0}, - ], + properties: { + name': 'string', + birthday: 'date', + friends: {type: 'list', objectType: 'Person'}, + points: {type: 'int', default: 0}, + }, }; const realm = new Realm({schema: [personSchema]}); @@ -101,27 +101,28 @@ The `realmConfig` passed to the constructor can contain the following: ### ObjectSchema - `name` – string used to refer to this object type -- `properties` - array of property defitions (see below) +- `properties` - object with property definitions (see below) - `primaryKey` – optional - name of `STRING` or `INT` property that should be unique ### Property Types -When definining object `properties` in a `schema`, each should have a unique `name`, and the `type` of each property must be defined as either the name of an object type in the same schema **or** as one of the following: +When defining object `properties` in a `schema`, the value for each property must either be the `type` **OR** an object with a `type` key along with other options detailed below. The `type` of each property must be defined as either the name of an object type in the same schema **or** as one of the following: -- `Realm.Types.BOOL` -- `Realm.Types.INT` -- `Realm.Types.FLOAT` -- `Realm.Types.DOUBLE` -- `Realm.Types.STRING` -- `Realm.Types.DATE` -- `Realm.Types.DATA` -- `Realm.Types.OBJECT` (requires `objectType`, is always `optional`) -- `Realm.Types.LIST` (requires `objectType`, is never `optional`) +- `Realm.Types.BOOL` (`"bool"`) +- `Realm.Types.INT` (`"int"`) +- `Realm.Types.FLOAT` (`"float"`) +- `Realm.Types.DOUBLE` (`"double"`) +- `Realm.Types.STRING` (`"string"`) +- `Realm.Types.DATE` (`"date"`) +- `Realm.Types.DATA` (`"data"`) +- `Realm.Types.LIST` (`"list"` – requires `objectType` and cannot be `optional`) You _may_ specify these property options as well: - `default` – default value when property was not specified on creation - `optional` – boolean indicating if this property may be assigned `null` or `undefined` +**Note:** When the `type` of a property is that of an object type in the same schema, it _always_ will be `optional`. + ### `Realm` Instance Methods #### `create(type, props [, update])` - `type` – string matching object `name` in the `schema` definition From c34990759ddd8b5cb1d57605c65374122650422f Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 4 Jan 2016 16:10:48 -0800 Subject: [PATCH 13/15] Add test that checks schema validation --- tests/lib/realm-tests.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/lib/realm-tests.js b/tests/lib/realm-tests.js index 8bde87cd..b044143e 100644 --- a/tests/lib/realm-tests.js +++ b/tests/lib/realm-tests.js @@ -66,6 +66,28 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(realm.objects('TestObject')[0].doubleCol, 1) }, + testRealmConstructorSchemaValidation: function() { + TestCase.assertThrows(function() { + new Realm({schema: schemas.AllTypes}); + }, 'The schema should be an array'); + + TestCase.assertThrows(function() { + new Realm({schema: ['SomeType']}); + }, 'The schema should be an array of objects'); + + TestCase.assertThrows(function() { + new Realm({schema: [{}]}); + }, 'The schema should be an array of ObjectSchema objects'); + + TestCase.assertThrows(function() { + new Realm({schema: [{name: 'SomeObject'}]}); + }, 'The schema should be an array of ObjectSchema objects'); + + TestCase.assertThrows(function() { + new Realm({schema: [{properties: {intCol: Realm.Types.INT}}]}); + }, 'The schema should be an array of ObjectSchema objects'); + }, + testDefaultPath: function() { var defaultRealm = new Realm({schema: []}); TestCase.assertEqual(defaultRealm.path, Realm.defaultPath); From 1922125ab0e492ca447356da77462b22e43db396 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Wed, 13 Jan 2016 14:53:39 -0800 Subject: [PATCH 14/15] Improve clarity of schema parsing and serialization --- src/js_schema.cpp | 36 +++++++++++------------------------- src/rpc.cpp | 1 + 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/src/js_schema.cpp b/src/js_schema.cpp index 757523fd..6dbced29 100644 --- a/src/js_schema.cpp +++ b/src/js_schema.cpp @@ -140,36 +140,22 @@ static inline ObjectSchema RJSParseObjectSchema(JSContextRef ctx, JSObjectRef ob objectSchema.name = RJSValidatedStringProperty(ctx, objectSchemaObject, nameString); JSObjectRef propertiesObject = RJSValidatedObjectProperty(ctx, objectSchemaObject, propertiesString, "ObjectSchema must have a 'properties' object."); - JSPropertyNameArrayRef propertyNames = NULL; - size_t propertyCount; - if (RJSIsValueArray(ctx, propertiesObject)) { - propertyCount = RJSValidatedListLength(ctx, propertiesObject); - } - else { - propertyNames = JSObjectCopyPropertyNames(ctx, propertiesObject); - propertyCount = JSPropertyNameArrayGetCount(propertyNames); - } - - for (size_t i = 0; i < propertyCount; i++) { - Property property; - - // Check if the properties were provided as an object. - if (propertyNames) { - JSStringRef propertyName = JSPropertyNameArrayGetNameAtIndex(propertyNames, i); - JSValueRef propertyValue = RJSValidatedPropertyValue(ctx, propertiesObject, propertyName); - property = RJSParseProperty(ctx, propertyValue, RJSStringForJSString(propertyName), objectDefaults); - } - else { + size_t propertyCount = RJSValidatedListLength(ctx, propertiesObject); + for (size_t i = 0; i < propertyCount; i++) { JSObjectRef propertyObject = RJSValidatedObjectAtIndex(ctx, propertiesObject, (unsigned int)i); std::string propertyName = RJSValidatedStringProperty(ctx, propertyObject, nameString); - property = RJSParseProperty(ctx, propertyObject, propertyName, objectDefaults); + objectSchema.properties.emplace_back(RJSParseProperty(ctx, propertyObject, propertyName, objectDefaults)); } - - objectSchema.properties.emplace_back(property); } - - if (propertyNames) { + else { + JSPropertyNameArrayRef propertyNames = JSObjectCopyPropertyNames(ctx, propertiesObject); + size_t propertyCount = JSPropertyNameArrayGetCount(propertyNames); + for (size_t i = 0; i < propertyCount; i++) { + JSStringRef propertyName = JSPropertyNameArrayGetNameAtIndex(propertyNames, i); + JSValueRef propertyValue = RJSValidatedPropertyValue(ctx, propertiesObject, propertyName); + objectSchema.properties.emplace_back(RJSParseProperty(ctx, propertyValue, RJSStringForJSString(propertyName), objectDefaults)); + } JSPropertyNameArrayRelease(propertyNames); } diff --git a/src/rpc.cpp b/src/rpc.cpp index db35c3df..2abad2e9 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -269,6 +269,7 @@ json RPCServer::serialize_json_value(JSValueRef value) { }; } else { + // Serialize this JS object as a plain object since it doesn't match any known types above. JSPropertyNameArrayRef js_keys = JSObjectCopyPropertyNames(m_context, js_object); size_t count = JSPropertyNameArrayGetCount(js_keys); std::vector keys; From b82997efc023ebada2b4afcedc769383797584d6 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Thu, 14 Jan 2016 15:23:17 -0800 Subject: [PATCH 15/15] Fix typo in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 26429e26..158d1240 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ const personSchema = { name: 'Person', primaryKey: 'name', properties: { - name': 'string', + name: 'string', birthday: 'date', friends: {type: 'list', objectType: 'Person'}, points: {type: 'int', default: 0},