Merge pull request #257 from realm/sk-schema-prop

The schema property should be directly on constructor
This commit is contained in:
Scott Kyle 2016-02-18 16:27:47 -08:00
commit 2e3a3b5db8
9 changed files with 110 additions and 31 deletions

View File

@ -32,6 +32,13 @@ function create(realmId, info) {
}); });
}); });
if (constructor) {
let result = constructor.call(object);
if (result != null && result != object) {
throw new Error('Realm object constructor must not return another value');
}
}
return object; return object;
} }

View File

@ -20,16 +20,27 @@ rpc.registerTypeConverter(objectTypes.RESULTS, results.create);
class Realm { class Realm {
constructor(config) { constructor(config) {
let schema = typeof config == 'object' && config.schema; let schemas = typeof config == 'object' && config.schema;
let constructors = {}; let constructors = {};
for (let i = 0, len = schema ? schema.length : 0; i < len; i++) { for (let i = 0, len = schemas ? schemas.length : 0; i < len; i++) {
let item = schema[i]; let item = schemas[i];
let proto = item.prototype;
if (proto && proto.schema) { if (typeof item == 'function') {
schema.splice(i, 1, proto.schema); let schema = item.schema;
constructors[proto.schema.name] = item; if (!schema || typeof schema != 'object') {
throw new Error("Realm object constructor must have 'schema' property");
}
let {name, properties} = schema;
if (!name || typeof name != 'string') {
throw new Error("Realm object schema must have 'name' property");
} else if (!properties || typeof properties != 'object') {
throw new Error("Realm object schema must have 'properties' property");
}
schemas.splice(i, 1, schema);
constructors[name] = item;
} }
} }

View File

@ -58,8 +58,24 @@ JSClassRef RJSObjectClass() {
} }
JSObjectRef RJSObjectCreate(JSContextRef ctx, Object object) { JSObjectRef RJSObjectCreate(JSContextRef ctx, Object object) {
JSValueRef prototype = RJSPrototypes(object.realm().get())[object.get_object_schema().name]; static JSStringRef prototypeString = JSStringCreateWithUTF8CString("prototype");
JSObjectRef constructor = RJSConstructors(object.realm().get())[object.get_object_schema().name];
JSObjectRef prototype = constructor ? RJSValidatedObjectProperty(ctx, constructor, prototypeString) : NULL;
JSObjectRef jsObject = RJSWrapObject(ctx, RJSObjectClass(), new Object(object), prototype); JSObjectRef jsObject = RJSWrapObject(ctx, RJSObjectClass(), new Object(object), prototype);
if (constructor) {
JSValueRef exception = NULL;
JSValueRef result = JSObjectCallAsFunction(ctx, constructor, jsObject, 0, NULL, &exception);
if (exception) {
throw RJSException(ctx, exception);
}
else if (result != jsObject && !JSValueIsNull(ctx, result) && !JSValueIsUndefined(ctx, result)) {
throw std::runtime_error("Realm object constructor must not return another value");
}
}
return jsObject; return jsObject;
} }

View File

@ -39,8 +39,8 @@ public:
~RJSRealmDelegate() { ~RJSRealmDelegate() {
remove_all_notifications(); remove_all_notifications();
for (auto prototype : m_prototypes) { for (auto constructor : m_constructors) {
JSValueUnprotect(m_context, prototype.second); JSValueUnprotect(m_context, constructor.second);
} }
for (auto objectDefaults : m_defaults) { for (auto objectDefaults : m_defaults) {
for (auto value : objectDefaults.second) { for (auto value : objectDefaults.second) {
@ -70,7 +70,7 @@ public:
} }
std::map<std::string, ObjectDefaults> m_defaults; std::map<std::string, ObjectDefaults> m_defaults;
std::map<std::string, JSValueRef> m_prototypes; std::map<std::string, JSObjectRef> m_constructors;
private: private:
std::set<JSObjectRef> m_notifications; std::set<JSObjectRef> m_notifications;
@ -101,8 +101,8 @@ std::map<std::string, ObjectDefaults> &RJSDefaults(Realm *realm) {
return static_cast<RJSRealmDelegate *>(realm->m_binding_context.get())->m_defaults; return static_cast<RJSRealmDelegate *>(realm->m_binding_context.get())->m_defaults;
} }
std::map<std::string, JSValueRef> &RJSPrototypes(Realm *realm) { std::map<std::string, JSObjectRef> &RJSConstructors(Realm *realm) {
return static_cast<RJSRealmDelegate *>(realm->m_binding_context.get())->m_prototypes; return static_cast<RJSRealmDelegate *>(realm->m_binding_context.get())->m_constructors;
} }
// static std::string s_defaultPath = realm::default_realm_file_directory() + "/default.realm"; // static std::string s_defaultPath = realm::default_realm_file_directory() + "/default.realm";
@ -137,7 +137,7 @@ JSObjectRef RealmConstructor(JSContextRef ctx, JSObjectRef constructor, size_t a
try { try {
Realm::Config config; Realm::Config config;
std::map<std::string, realm::ObjectDefaults> defaults; std::map<std::string, realm::ObjectDefaults> defaults;
std::map<std::string, JSValueRef> prototypes; std::map<std::string, JSObjectRef> constructors;
switch (argumentCount) { switch (argumentCount) {
case 0: case 0:
config.path = RJSDefaultPath(); config.path = RJSDefaultPath();
@ -163,7 +163,7 @@ JSObjectRef RealmConstructor(JSContextRef ctx, JSObjectRef constructor, size_t a
static JSStringRef schemaString = JSStringCreateWithUTF8CString("schema"); static JSStringRef schemaString = JSStringCreateWithUTF8CString("schema");
JSValueRef schemaValue = RJSValidatedPropertyValue(ctx, object, schemaString); JSValueRef schemaValue = RJSValidatedPropertyValue(ctx, object, schemaString);
if (!JSValueIsUndefined(ctx, schemaValue)) { if (!JSValueIsUndefined(ctx, schemaValue)) {
config.schema.reset(new Schema(RJSParseSchema(ctx, RJSValidatedValueToObject(ctx, schemaValue), defaults, prototypes))); config.schema.reset(new Schema(RJSParseSchema(ctx, RJSValidatedValueToObject(ctx, schemaValue), defaults, constructors)));
} }
static JSStringRef schemaVersionString = JSStringCreateWithUTF8CString("schemaVersion"); static JSStringRef schemaVersionString = JSStringCreateWithUTF8CString("schemaVersion");
@ -187,7 +187,7 @@ JSObjectRef RealmConstructor(JSContextRef ctx, JSObjectRef constructor, size_t a
realm->m_binding_context.reset(new RJSRealmDelegate(realm, JSContextGetGlobalContext(ctx))); realm->m_binding_context.reset(new RJSRealmDelegate(realm, JSContextGetGlobalContext(ctx)));
} }
RJSDefaults(realm.get()) = defaults; RJSDefaults(realm.get()) = defaults;
RJSPrototypes(realm.get()) = prototypes; RJSConstructors(realm.get()) = constructors;
return RJSWrapObject<SharedRealm *>(ctx, RJSRealmClass(), new SharedRealm(realm)); return RJSWrapObject<SharedRealm *>(ctx, RJSRealmClass(), new SharedRealm(realm));
} }
catch (std::exception &ex) { catch (std::exception &ex) {

View File

@ -19,4 +19,4 @@ std::string RJSDefaultPath();
void RJSSetDefaultPath(std::string path); void RJSSetDefaultPath(std::string path);
std::map<std::string, realm::ObjectDefaults> &RJSDefaults(realm::Realm *realm); std::map<std::string, realm::ObjectDefaults> &RJSDefaults(realm::Realm *realm);
std::map<std::string, JSValueRef> &RJSPrototypes(realm::Realm *realm); std::map<std::string, JSObjectRef> &RJSConstructors(realm::Realm *realm);

View File

@ -114,19 +114,17 @@ static inline Property RJSParseProperty(JSContextRef ctx, JSValueRef propertyAtt
return prop; return prop;
} }
static inline ObjectSchema RJSParseObjectSchema(JSContextRef ctx, JSObjectRef objectSchemaObject, std::map<std::string, realm::ObjectDefaults> &defaults, std::map<std::string, JSValueRef> &prototypes) { static inline ObjectSchema RJSParseObjectSchema(JSContextRef ctx, JSObjectRef objectSchemaObject, std::map<std::string, realm::ObjectDefaults> &defaults, std::map<std::string, JSObjectRef> &constructors) {
static JSStringRef nameString = JSStringCreateWithUTF8CString("name"); static JSStringRef nameString = JSStringCreateWithUTF8CString("name");
static JSStringRef primaryString = JSStringCreateWithUTF8CString("primaryKey"); static JSStringRef primaryString = JSStringCreateWithUTF8CString("primaryKey");
static JSStringRef prototypeString = JSStringCreateWithUTF8CString("prototype");
static JSStringRef propertiesString = JSStringCreateWithUTF8CString("properties"); static JSStringRef propertiesString = JSStringCreateWithUTF8CString("properties");
static JSStringRef schemaString = JSStringCreateWithUTF8CString("schema"); static JSStringRef schemaString = JSStringCreateWithUTF8CString("schema");
JSObjectRef prototypeObject = NULL; JSObjectRef objectConstructor = NULL;
JSValueRef prototypeValue = RJSValidatedPropertyValue(ctx, objectSchemaObject, prototypeString);
if (!JSValueIsUndefined(ctx, prototypeValue)) { if (JSObjectIsFunction(ctx, objectSchemaObject) || JSObjectIsConstructor(ctx, objectSchemaObject)) {
prototypeObject = RJSValidatedValueToObject(ctx, prototypeValue); objectConstructor = objectSchemaObject;
objectSchemaObject = RJSValidatedObjectProperty(ctx, prototypeObject, schemaString, "Realm object prototype must have a 'schema' property."); objectSchemaObject = RJSValidatedObjectProperty(ctx, objectConstructor, schemaString, "Realm object constructor must have a 'schema' property.");
} }
else { else {
JSValueRef subSchemaValue = RJSValidatedPropertyValue(ctx, objectSchemaObject, schemaString); JSValueRef subSchemaValue = RJSValidatedPropertyValue(ctx, objectSchemaObject, schemaString);
@ -170,9 +168,9 @@ static inline ObjectSchema RJSParseObjectSchema(JSContextRef ctx, JSObjectRef ob
} }
// Store prototype so that objects of this type will have their prototype set to this prototype object. // Store prototype so that objects of this type will have their prototype set to this prototype object.
if (prototypeObject) { if (objectConstructor) {
JSValueProtect(ctx, prototypeObject); JSValueProtect(ctx, objectConstructor);
prototypes[objectSchema.name] = std::move(prototypeObject); constructors[objectSchema.name] = std::move(objectConstructor);
} }
defaults.emplace(objectSchema.name, std::move(objectDefaults)); defaults.emplace(objectSchema.name, std::move(objectDefaults));
@ -180,12 +178,12 @@ static inline ObjectSchema RJSParseObjectSchema(JSContextRef ctx, JSObjectRef ob
return objectSchema; return objectSchema;
} }
realm::Schema RJSParseSchema(JSContextRef ctx, JSObjectRef jsonObject, std::map<std::string, realm::ObjectDefaults> &defaults, std::map<std::string, JSValueRef> &prototypes) { realm::Schema RJSParseSchema(JSContextRef ctx, JSObjectRef jsonObject, std::map<std::string, realm::ObjectDefaults> &defaults, std::map<std::string, JSObjectRef> &constructors) {
std::vector<ObjectSchema> schema; std::vector<ObjectSchema> schema;
size_t length = RJSValidatedListLength(ctx, jsonObject); size_t length = RJSValidatedListLength(ctx, jsonObject);
for (unsigned int i = 0; i < length; i++) { for (unsigned int i = 0; i < length; i++) {
JSObjectRef jsonObjectSchema = RJSValidatedObjectAtIndex(ctx, jsonObject, i); JSObjectRef jsonObjectSchema = RJSValidatedObjectAtIndex(ctx, jsonObject, i);
ObjectSchema objectSchema = RJSParseObjectSchema(ctx, jsonObjectSchema, defaults, prototypes); ObjectSchema objectSchema = RJSParseObjectSchema(ctx, jsonObjectSchema, defaults, constructors);
schema.emplace_back(std::move(objectSchema)); schema.emplace_back(std::move(objectSchema));
} }

View File

@ -15,4 +15,4 @@ namespace realm {
JSClassRef RJSSchemaClass(); JSClassRef RJSSchemaClass();
JSObjectRef RJSSchemaCreate(JSContextRef ctx, realm::Schema *schema); JSObjectRef RJSSchemaCreate(JSContextRef ctx, realm::Schema *schema);
realm::Schema RJSParseSchema(JSContextRef ctx, JSObjectRef jsonObject, std::map<std::string, realm::ObjectDefaults> &defaults, std::map<std::string, JSValueRef> &prototypes); realm::Schema RJSParseSchema(JSContextRef ctx, JSObjectRef jsonObject, std::map<std::string, realm::ObjectDefaults> &defaults, std::map<std::string, JSObjectRef> &constructors);

View File

@ -261,6 +261,53 @@ module.exports = BaseTest.extend({
}); });
}, },
testRealmCreateWithConstructor: function() {
var customCreated = 0;
function CustomObject() {
customCreated++;
this.intCol *= 100;
}
CustomObject.schema = {
name: 'CustomObject',
properties: {
intCol: 'int'
}
}
function InvalidObject() {
return {};
}
TestCase.assertThrows(function() {
new Realm({schema: [InvalidObject]});
});
InvalidObject.schema = {
name: 'InvalidObject',
properties: {
intCol: 'int'
}
}
var realm = new Realm({schema: [CustomObject, InvalidObject]});
realm.write(function() {
var object = realm.create('CustomObject', {intCol: 1});
TestCase.assertTrue(object instanceof CustomObject);
TestCase.assertTrue(Object.getPrototypeOf(object) == CustomObject.prototype);
TestCase.assertEqual(customCreated, 1);
// Should have been multiplied by 100 in the constructor.
TestCase.assertEqual(object.intCol, 100);
});
TestCase.assertThrows(function() {
realm.write(function() {
realm.create('InvalidObject', {intCol: 1});
});
});
},
testRealmDelete: function() { testRealmDelete: function() {
var realm = new Realm({schema: [schemas.TestObject]}); var realm = new Realm({schema: [schemas.TestObject]});

View File

@ -14,7 +14,7 @@ exports.TestObject = {
}; };
function PersonObject() {} function PersonObject() {}
PersonObject.prototype.schema = { PersonObject.schema = {
name: 'PersonObject', name: 'PersonObject',
properties: { properties: {
name: Realm.Types.STRING, name: Realm.Types.STRING,