diff --git a/docs/realm.js b/docs/realm.js index 54e5359b..d150cdfc 100644 --- a/docs/realm.js +++ b/docs/realm.js @@ -27,9 +27,9 @@ class Realm { * Create a new `Realm` instance using the provided `config`. If a Realm does not yet exist * at `config.path` (or {@link Realm.defaultPath} if not provided), then this constructor * will create it with the provided `config.schema` (which is _required_ in this case). - * Otherwise, the instance will access the existing realm from the file at that path. + * Otherwise, the instance will access the existing Realm from the file at that path. * In this case, `config.schema` is _optional_ or not have changed, unless - * `config.schemaVersion` is incremented, in which case the realm will be automatically + * `config.schemaVersion` is incremented, in which case the Realm will be automatically * migrated to use the new schema. * @param {Realm~Configuration} [config] - **Required** when first creating the Realm. */ @@ -37,8 +37,7 @@ class Realm { /** * Create a new Realm object of the given type and with the specified properties. - * @param {string} type - The type of object as specified by its `name` in the - * {@link Realm~ObjectSchema ObjectSchema} definition. + * @param {Realm~ObjectType} type - The type of Realm object to create. * @param {Object} properties - Property values for all required properties without a * default value. * @param {boolean} [update=false] - Signals that an existing object with matching primary key @@ -61,12 +60,11 @@ class Realm { /** * Returns all objects of the given `type` in the Realm. - * @param {string} type - The type of object as specified by its `name` in the - * {@link Realm~ObjectSchema ObjectSchema} definition. + * @param {Realm~ObjectType} type - The type of Realm objects to retrieve. * @throws {Error} If type passed into this method is invalid. * @returns {Realm.Results} that will live-update as objects are created and destroyed. */ - objects(type, query, ...arg) {} + objects(type) {} /** * Add a listener `callback` for the specified event `name`. @@ -116,12 +114,20 @@ Realm.defaultPath; * @type {Object} * @property {string} [path={@link Realm.defaultPath}] - The path to the file where the * Realm database should be stored. - * @property {Realm~ObjectSchema[]} [schema] - Specifies all the object types in the realm. - * **Required** when first creating realm at this `path`. + * @property {Array} [schema] - Specifies all the + * object types in this Realm. **Required** when first creating a Realm at this `path`. * @property {number} [schemaVersion] - **Required** (and must be incremented) after * changing the `schema`. */ +/** + * Realm objects will inherit methods, getters, and setters from the `prototype` of this + * constructor. + * @typedef Realm~ObjectClass + * @type {Class} + * @property {Realm~ObjectSchema} schema - Static property specifying object schema information. + */ + /** * @typedef Realm~ObjectSchema * @type {Object} @@ -143,6 +149,14 @@ Realm.defaultPath; * @property {boolean} [optional] - Signals if this property may be assigned `null` or `undefined`. */ +/** + * The type of an object may either be specified as a string equal to the `name` in a + * {@link Realm~ObjectSchema ObjectSchema} definition, **or** a constructor that was specified + * in the {@link Realm~Configuration configuration} `schema`. + * @typedef Realm~ObjectType + * @type {string|Realm~ObjectClass} + */ + /** * A property type may be specified as one of the standard builtin types, or as an object type * inside the same schema. diff --git a/lib/browser/index.js b/lib/browser/index.js index d160af58..a4f9dd46 100644 --- a/lib/browser/index.js +++ b/lib/browser/index.js @@ -74,6 +74,24 @@ export default class Realm { }); } + create(type, ...args) { + if (typeof type == 'function') { + type = objects.typeForConstructor(this[keys.realm], type); + } + + let method = util.createMethod(objectTypes.REALM, 'create', true); + return method.apply(this, [type, ...args]); + } + + objects(type, ...args) { + if (typeof type == 'function') { + type = objects.typeForConstructor(this[keys.realm], type); + } + + let method = util.createMethod(objectTypes.REALM, 'objects'); + return method.apply(this, [type, ...args]); + } + addListener(name, callback) { if (typeof callback != 'function') { throw new Error('Realm.addListener must be passed a function!'); @@ -130,12 +148,10 @@ export default class Realm { // Non-mutating methods: util.createMethods(Realm.prototype, objectTypes.REALM, [ 'close', - 'objects', ]); // Mutating methods: util.createMethods(Realm.prototype, objectTypes.REALM, [ - 'create', 'delete', 'deleteAll', ], true); diff --git a/lib/browser/objects.js b/lib/browser/objects.js index 974b4916..f0f78220 100644 --- a/lib/browser/objects.js +++ b/lib/browser/objects.js @@ -53,3 +53,15 @@ export function create(realmId, info) { export function registerConstructors(realmId, constructors) { registeredConstructors[realmId] = constructors; } + +export function typeForConstructor(realmId, constructor) { + let constructors = registeredConstructors[realmId]; + + for (let name in constructors) { + if (constructors[name] == constructor) { + return name; + } + } + + return null; +} diff --git a/src/js_realm.cpp b/src/js_realm.cpp index 6002a78b..e87b4977 100644 --- a/src/js_realm.cpp +++ b/src/js_realm.cpp @@ -254,11 +254,28 @@ JSValueRef RealmGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef pr return NULL; } +std::string RJSValidatedObjectTypeForValue(SharedRealm &realm, JSContextRef ctx, JSValueRef value) { + if (JSValueIsObject(ctx, value) && JSObjectIsConstructor(ctx, (JSObjectRef)value)) { + JSObjectRef constructor = (JSObjectRef)value; + + for (auto pair : RJSConstructors(realm.get())) { + if (pair.second == constructor) { + return pair.first; + } + } + + throw std::runtime_error("Constructor was not registered in the schema for this Realm"); + } + + return RJSValidatedStringForValue(ctx, value, "objectType"); +} + JSValueRef RealmObjects(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { try { - RJSValidateArgumentCount(1, argumentCount); - std::string className = RJSValidatedStringForValue(ctx, arguments[0], "objectType"); + RJSValidateArgumentCount(argumentCount, 1); + SharedRealm sharedRealm = *RJSGetInternal(thisObject); + std::string className = RJSValidatedObjectTypeForValue(sharedRealm, ctx, arguments[0]); return RJSResultsCreate(ctx, sharedRealm, className); } catch (std::exception &exp) { @@ -296,10 +313,12 @@ JSValueRef RealmCreateObject(JSContextRef ctx, JSObjectRef function, JSObjectRef try { RJSValidateArgumentRange(argumentCount, 2, 3); - std::string className = RJSValidatedStringForValue(ctx, arguments[0], "objectType"); SharedRealm sharedRealm = *RJSGetInternal(thisObject); - auto object_schema = sharedRealm->config().schema->find(className); - if (object_schema == sharedRealm->config().schema->end()) { + std::string className = RJSValidatedObjectTypeForValue(sharedRealm, ctx, arguments[0]); + auto &schema = sharedRealm->config().schema; + auto object_schema = schema->find(className); + + if (object_schema == schema->end()) { *jsException = RJSMakeError(ctx, "Object type '" + className + "' not found in schema."); return NULL; } diff --git a/src/js_schema.cpp b/src/js_schema.cpp index 529d282e..3bd4dfe5 100644 --- a/src/js_schema.cpp +++ b/src/js_schema.cpp @@ -136,16 +136,10 @@ static inline ObjectSchema RJSParseObjectSchema(JSContextRef ctx, JSObjectRef ob JSObjectRef objectConstructor = NULL; - if (JSObjectIsFunction(ctx, objectSchemaObject) || JSObjectIsConstructor(ctx, objectSchemaObject)) { + if (JSObjectIsConstructor(ctx, objectSchemaObject)) { objectConstructor = objectSchemaObject; objectSchemaObject = RJSValidatedObjectProperty(ctx, objectConstructor, schemaString, "Realm object constructor must have a 'schema' property."); } - else { - JSValueRef subSchemaValue = RJSValidatedPropertyValue(ctx, objectSchemaObject, schemaString); - if (!JSValueIsUndefined(ctx, subSchemaValue)) { - objectSchemaObject = RJSValidatedValueToObject(ctx, subSchemaValue); - } - } ObjectDefaults objectDefaults; ObjectSchema objectSchema; diff --git a/tests/js/realm-tests.js b/tests/js/realm-tests.js index 858eeb62..99da3b86 100644 --- a/tests/js/realm-tests.js +++ b/tests/js/realm-tests.js @@ -317,6 +317,13 @@ module.exports = BaseTest.extend({ // Should have been multiplied by 100 in the constructor. TestCase.assertEqual(object.intCol, 100); + + // Should be able to create object by passing in constructor. + object = realm.create(CustomObject, {intCol: 2}); + TestCase.assertTrue(object instanceof CustomObject); + TestCase.assertTrue(Object.getPrototypeOf(object) == CustomObject.prototype); + TestCase.assertEqual(customCreated, 2); + TestCase.assertEqual(object.intCol, 200); }); TestCase.assertThrows(function() { @@ -324,6 +331,16 @@ module.exports = BaseTest.extend({ realm.create('InvalidObject', {intCol: 1}); }); }); + + // Only the original constructor should be valid. + function InvalidCustomObject() {} + InvalidCustomObject.schema = CustomObject.schema; + + TestCase.assertThrows(function() { + realm.write(function() { + realm.create(InvalidCustomObject, {intCol: 1}); + }); + }); }, testRealmDelete: function() { @@ -389,6 +406,7 @@ module.exports = BaseTest.extend({ testRealmObjects: function() { var realm = new Realm({schema: [schemas.PersonObject, schemas.DefaultValues, schemas.TestObject]}); + realm.write(function() { realm.create('PersonObject', {name: 'Ari', age: 10}); realm.create('PersonObject', {name: 'Tim', age: 11}); @@ -396,18 +414,28 @@ module.exports = BaseTest.extend({ realm.create('PersonObject', {name: 'Alex', age: 12, married: true}); }); - TestCase.assertThrows(function() { + // Should be able to pass constructor for getting objects. + var objects = realm.objects(schemas.PersonObject); + TestCase.assertTrue(objects[0] instanceof schemas.PersonObject); + + function InvalidPerson() {} + InvalidPerson.schema = schemas.PersonObject.schema; + + TestCase.assertThrows(function() { realm.objects(); }); - TestCase.assertThrows(function() { + TestCase.assertThrows(function() { realm.objects([]); }); - TestCase.assertThrows(function() { + TestCase.assertThrows(function() { realm.objects('InvalidClass'); }); - TestCase.assertThrows(function() { + TestCase.assertThrows(function() { realm.objects('PersonObject', 'truepredicate'); }); + TestCase.assertThrows(function() { + realm.objects(InvalidPerson); + }); }, testNotifications: function() {