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'; import Realm from 'realm';
class Todo {} class Todo extends Realm.Object {}
Todo.schema = { Todo.schema = {
name: 'Todo', name: 'Todo',
properties: { properties: {
done: {type: Realm.Types.BOOL, default: false}, done: {type: 'bool', default: false},
text: Realm.Types.STRING, text: 'string',
}, },
}; };
class TodoList {}
class TodoList extends Realm.Object {}
TodoList.schema = { TodoList.schema = {
name: 'TodoList', name: 'TodoList',
properties: { properties: {
name: Realm.Types.STRING, name: 'string',
items: {type: Realm.Types.LIST, objectType: 'Todo', default: []}, items: {type: 'list', objectType: 'Todo'},
}, },
}; };
export default new Realm({schema: [Todo, TodoList]}); export default new Realm({schema: [Todo, TodoList]});

View File

@ -48,10 +48,7 @@ class ObjectWrap {
} }
static JSObjectRef create_constructor(JSContextRef ctx) { static JSObjectRef create_constructor(JSContextRef ctx) {
if (JSClassRef constructor_class = get_constructor_class()) { return JSObjectMake(ctx, get_constructor_class(), nullptr);
return JSObjectMake(ctx, constructor_class, nullptr);
}
return JSObjectMakeConstructor(ctx, get_class(), construct);
} }
static JSClassRef get_class() { static JSClassRef get_class() {
@ -92,7 +89,9 @@ class ObjectWrap {
static std::vector<JSStaticFunction> get_methods(const MethodMap &); static std::vector<JSStaticFunction> get_methods(const MethodMap &);
static std::vector<JSStaticValue> get_properties(const PropertyMap &); 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 JSObjectRef construct(JSContextRef, JSObjectRef, size_t, const JSValueRef[], JSValueRef*);
static void initialize_constructor(JSContextRef, JSObjectRef);
static void finalize(JSObjectRef); static void finalize(JSObjectRef);
static void get_property_names(JSContextRef, JSObjectRef, JSPropertyNameAccumulatorRef); static void get_property_names(JSContextRef, JSObjectRef, JSPropertyNameAccumulatorRef);
static JSValueRef get_property(JSContextRef, JSObjectRef, JSStringRef, JSValueRef*); 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) { 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> template<typename ClassType>
inline JSClassRef ObjectWrap<ClassType>::create_constructor_class() { 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; JSClassDefinition definition = kJSClassDefinitionEmpty;
std::vector<JSStaticFunction> methods; std::vector<JSStaticFunction> methods;
std::vector<JSStaticValue> properties; std::vector<JSStaticValue> properties;
definition.attributes = kJSClassAttributeNoAutomaticPrototype; definition.attributes = kJSClassAttributeNoAutomaticPrototype;
definition.className = s_class.name.c_str(); definition.className = "Function";
definition.initialize = initialize_constructor;
definition.hasInstance = has_instance; definition.hasInstance = has_instance;
// This must be set for `typeof constructor` to be 'function'.
definition.callAsFunction = call;
if (s_class.constructor) { if (s_class.constructor) {
definition.callAsConstructor = construct; definition.callAsConstructor = construct;
} }
@ -220,22 +218,57 @@ inline std::vector<JSStaticValue> ObjectWrap<ClassType>::get_properties(const Pr
} }
template<typename ClassType> template<typename ClassType>
inline JSObjectRef ObjectWrap<ClassType>::construct(JSContextRef ctx, JSObjectRef constructor, size_t argc, const JSValueRef arguments[], JSValueRef* exception) { inline JSValueRef ObjectWrap<ClassType>::call(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], JSValueRef* exception) {
if (!s_class.constructor) { // This should only be called as a super() call in the constructor of a subclass.
*exception = jsc::Exception::value(ctx, "Illegal constructor"); if (!has_instance(ctx, this_object)) {
*exception = jsc::Exception::value(ctx, s_class.name + " cannot be called as a function");
return nullptr; 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 { try {
s_class.constructor(ctx, this_object, argc, arguments); s_class.constructor(ctx, this_object, argc, arguments);
} }
catch (std::exception &e) { catch (std::exception &e) {
*exception = jsc::Exception::value(ctx, e); *exception = jsc::Exception::value(ctx, e);
return nullptr;
} }
return this_object; 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> template<typename ClassType>
inline void ObjectWrap<ClassType>::finalize(JSObjectRef object) { inline void ObjectWrap<ClassType>::finalize(JSObjectRef object) {
// This is called for the most derived class before superclasses. // This is called for the most derived class before superclasses.

View File

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

View File

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

View File

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

View File

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