Merge pull request #289 from realm/sk-accept-constructor

Accept constructor in create() and objects() methods
This commit is contained in:
Scott Kyle 2016-03-07 12:23:26 -08:00
commit 1a127b425b
6 changed files with 110 additions and 27 deletions

View File

@ -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<Realm~ObjectClass|Realm~ObjectSchema>} [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.

View File

@ -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);

View File

@ -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;
}

View File

@ -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<SharedRealm *>(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<SharedRealm *>(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;
}

View File

@ -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;

View File

@ -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() {