Support ES6 class inheritance from Realm.Object

Unfortunately, this was broken because our native Realm.Object constructor was not presenting itself as a function in JS. This fixes that and adds tests.
This commit is contained in:
Scott Kyle 2016-05-09 16:15:10 -07:00
parent 651449108e
commit 26d2c169a9
6 changed files with 69 additions and 21 deletions

View File

@ -20,20 +20,22 @@
import Realm from 'realm';
class Todo {}
class Todo extends Realm.Object {}
Todo.schema = {
name: 'Todo',
properties: {
done: {type: Realm.Types.BOOL, default: false},
text: Realm.Types.STRING,
done: {type: 'bool', default: false},
text: 'string',
},
};
class TodoList {}
class TodoList extends Realm.Object {}
TodoList.schema = {
name: 'TodoList',
properties: {
name: Realm.Types.STRING,
items: {type: Realm.Types.LIST, objectType: 'Todo', default: []},
name: 'string',
items: {type: 'list', objectType: 'Todo'},
},
};
export default new Realm({schema: [Todo, TodoList]});

View File

@ -48,10 +48,7 @@ class ObjectWrap {
}
static JSObjectRef create_constructor(JSContextRef ctx) {
if (JSClassRef constructor_class = get_constructor_class()) {
return JSObjectMake(ctx, constructor_class, nullptr);
}
return JSObjectMakeConstructor(ctx, get_class(), construct);
return JSObjectMake(ctx, get_constructor_class(), nullptr);
}
static JSClassRef get_class() {
@ -92,7 +89,9 @@ class ObjectWrap {
static std::vector<JSStaticFunction> get_methods(const MethodMap &);
static std::vector<JSStaticValue> get_properties(const PropertyMap &);
static JSValueRef call(JSContextRef, JSObjectRef, JSObjectRef, size_t, const JSValueRef[], JSValueRef*);
static JSObjectRef construct(JSContextRef, JSObjectRef, size_t, const JSValueRef[], JSValueRef*);
static void initialize_constructor(JSContextRef, JSObjectRef);
static void finalize(JSObjectRef);
static void get_property_names(JSContextRef, JSObjectRef, JSPropertyNameAccumulatorRef);
static JSValueRef get_property(JSContextRef, JSObjectRef, JSStringRef, JSValueRef*);
@ -104,7 +103,7 @@ class ObjectWrap {
}
static bool has_instance(JSContextRef ctx, JSObjectRef constructor, JSValueRef value, JSValueRef* exception) {
return JSValueIsObjectOfClass(ctx, value, get_class());
return has_instance(ctx, value);
}
};
@ -158,19 +157,18 @@ inline JSClassRef ObjectWrap<ClassType>::create_class() {
template<typename ClassType>
inline JSClassRef ObjectWrap<ClassType>::create_constructor_class() {
// Skip creating a special constructor class if possible.
if (!s_class.constructor && s_class.static_methods.empty() && s_class.static_properties.empty()) {
return nullptr;
}
JSClassDefinition definition = kJSClassDefinitionEmpty;
std::vector<JSStaticFunction> methods;
std::vector<JSStaticValue> properties;
definition.attributes = kJSClassAttributeNoAutomaticPrototype;
definition.className = s_class.name.c_str();
definition.className = "Function";
definition.initialize = initialize_constructor;
definition.hasInstance = has_instance;
// This must be set for `typeof constructor` to be 'function'.
definition.callAsFunction = call;
if (s_class.constructor) {
definition.callAsConstructor = construct;
}
@ -220,22 +218,57 @@ inline std::vector<JSStaticValue> ObjectWrap<ClassType>::get_properties(const Pr
}
template<typename ClassType>
inline JSObjectRef ObjectWrap<ClassType>::construct(JSContextRef ctx, JSObjectRef constructor, size_t argc, const JSValueRef arguments[], JSValueRef* exception) {
if (!s_class.constructor) {
*exception = jsc::Exception::value(ctx, "Illegal constructor");
inline JSValueRef ObjectWrap<ClassType>::call(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], JSValueRef* exception) {
// This should only be called as a super() call in the constructor of a subclass.
if (!has_instance(ctx, this_object)) {
*exception = jsc::Exception::value(ctx, s_class.name + " cannot be called as a function");
return nullptr;
}
JSObjectRef this_object = ObjectWrap<ClassType>::create_instance(ctx);
// Classes without a constructor should still be subclassable.
if (s_class.constructor) {
try {
s_class.constructor(ctx, this_object, argc, arguments);
}
catch (std::exception &e) {
*exception = jsc::Exception::value(ctx, e);
return nullptr;
}
}
return JSValueMakeUndefined(ctx);
}
template<typename ClassType>
inline JSObjectRef ObjectWrap<ClassType>::construct(JSContextRef ctx, JSObjectRef constructor, size_t argc, const JSValueRef arguments[], JSValueRef* exception) {
if (!s_class.constructor) {
*exception = jsc::Exception::value(ctx, s_class.name + " is not a constructor");
return nullptr;
}
JSObjectRef this_object = create_instance(ctx);
try {
s_class.constructor(ctx, this_object, argc, arguments);
}
catch (std::exception &e) {
*exception = jsc::Exception::value(ctx, e);
return nullptr;
}
return this_object;
}
template<typename ClassType>
inline void ObjectWrap<ClassType>::initialize_constructor(JSContextRef ctx, JSObjectRef constructor) {
static const String prototype_string = "prototype";
// Set the prototype of the constructor to be Function.prototype.
Object::set_prototype(ctx, constructor, Object::get_prototype(ctx, JSObjectMakeFunctionWithCallback(ctx, nullptr, call)));
// Set the constructor prototype to be the prototype generated from the instance JSClassRef.
JSObjectRef prototype = Object::validated_get_object(ctx, JSObjectMakeConstructor(ctx, get_class(), construct), prototype_string);
Object::set_property(ctx, constructor, prototype_string, prototype, js::ReadOnly | js::DontEnum | js::DontDelete);
}
template<typename ClassType>
inline void ObjectWrap<ClassType>::finalize(JSObjectRef object) {
// This is called for the most derived class before superclasses.

View File

@ -36,6 +36,9 @@ module.exports = BaseTest.extend({
TestCase.assertThrows(function() {
new Realm.List();
});
TestCase.assertEqual(typeof Realm.List, 'function');
TestCase.assertTrue(Realm.List instanceof Function);
},
testListLength: function() {

View File

@ -435,10 +435,14 @@ module.exports = BaseTest.extend({
testObjectConstructor: function() {
var realm = new Realm({schema: [schemas.TestObject]});
realm.write(function() {
var obj = realm.create('TestObject', {doubleCol: 1});
TestCase.assertTrue(obj instanceof Realm.Object);
});
TestCase.assertEqual(typeof Realm.Object, 'function');
TestCase.assertTrue(Realm.Object instanceof Function);
},
testIsValid: function() {

View File

@ -27,6 +27,9 @@ module.exports = BaseTest.extend({
testRealmConstructor: function() {
var realm = new Realm({schema: []});
TestCase.assertTrue(realm instanceof Realm);
TestCase.assertEqual(typeof Realm, 'function');
TestCase.assertTrue(Realm instanceof Function);
},
testRealmConstructorPath: function() {

View File

@ -34,6 +34,9 @@ module.exports = BaseTest.extend({
TestCase.assertThrows(function() {
new Realm.Results();
});
TestCase.assertEqual(typeof Realm.Results, 'function');
TestCase.assertTrue(Realm.Results instanceof Function);
},
testResultsLength: function() {