diff --git a/CHANGELOG.md b/CHANGELOG.md index 79745316..87a61d9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,11 @@ x.x.x Release notes (yyyy-MM-dd) ### Enhancements * Support for queries comparing optional properties to `null` +* `object.isValid()` has been added to enable checking if an object has been deleted ### Bugfixes -* None +* When accessing an empty Results `undefined` is returned rather than throwing an exception +* Accessing a deleted object throws a javascript exception rather than crashing 0.11.1 Release notes (2016-3-29) ============================================================= diff --git a/lib/browser/index.js b/lib/browser/index.js index 1830df93..64ca70b7 100644 --- a/lib/browser/index.js +++ b/lib/browser/index.js @@ -23,7 +23,7 @@ import { keys, propTypes, objectTypes } from './constants'; import Collection, * as collections from './collections'; import List, { createList } from './lists'; import Results, { createResults } from './results'; -import * as objects from './objects'; +import RealmObject, { createObject, registerConstructors, typeForConstructor } from './objects'; import * as rpc from './rpc'; import * as util from './util'; @@ -32,7 +32,7 @@ const listenersKey = Symbol(); rpc.registerTypeConverter(objectTypes.LIST, createList); rpc.registerTypeConverter(objectTypes.RESULTS, createResults); -rpc.registerTypeConverter(objectTypes.OBJECT, objects.create); +rpc.registerTypeConverter(objectTypes.OBJECT, createObject); export default class Realm { constructor(config) { @@ -62,7 +62,7 @@ export default class Realm { let realmId = rpc.createRealm(Array.from(arguments)); - objects.registerConstructors(realmId, constructors); + registerConstructors(realmId, constructors); this[keys.id] = realmId; this[keys.realm] = realmId; @@ -79,7 +79,7 @@ export default class Realm { create(type, ...args) { if (typeof type == 'function') { - type = objects.typeForConstructor(this[keys.realm], type); + type = typeForConstructor(this[keys.realm], type); } let method = util.createMethod(objectTypes.REALM, 'create', true); @@ -88,7 +88,7 @@ export default class Realm { objects(type, ...args) { if (typeof type == 'function') { - type = objects.typeForConstructor(this[keys.realm], type); + type = typeForConstructor(this[keys.realm], type); } let method = util.createMethod(objectTypes.REALM, 'objects'); @@ -169,6 +169,9 @@ Object.defineProperties(Realm, { Results: { value: Results, }, + Object: { + value: RealmObject, + }, defaultPath: { get: util.getterForProperty('defaultPath'), set: util.setterForProperty('defaultPath'), diff --git a/lib/browser/objects.js b/lib/browser/objects.js index f0f78220..30ed84c2 100644 --- a/lib/browser/objects.js +++ b/lib/browser/objects.js @@ -18,15 +18,23 @@ 'use strict'; -import { keys } from './constants'; -import { getterForProperty, setterForProperty } from './util'; +import { keys, objectTypes } from './constants'; +import { getterForProperty, setterForProperty, createMethods } from './util'; const registeredConstructors = {}; -export function create(realmId, info) { +export default class RealmObject { +} + +// Non-mutating methods: +createMethods(RealmObject.prototype, objectTypes.OBJECT, [ + 'isValid', +]); + +export function createObject(realmId, info) { let schema = info.schema; let constructor = (registeredConstructors[realmId] || {})[schema.name]; - let object = constructor ? Object.create(constructor.prototype) : {}; + let object = Object.create(constructor ? constructor.prototype : RealmObject.prototype); object[keys.realm] = realmId; object[keys.id] = info.id; diff --git a/src/js_realm.hpp b/src/js_realm.hpp index b3cb1351..ebba1bae 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -231,11 +231,13 @@ inline typename T::Function Realm::create_constructor(ContextType ctx) { FunctionType collection_constructor = ObjectWrap>::create_constructor(ctx); FunctionType list_constructor = ObjectWrap>::create_constructor(ctx); FunctionType results_constructor = ObjectWrap>::create_constructor(ctx); + FunctionType realm_object_constructor = ObjectWrap>::create_constructor(ctx); PropertyAttributes attributes = PropertyAttributes(ReadOnly | DontEnum | DontDelete); Object::set_property(ctx, realm_constructor, "Collection", collection_constructor, attributes); Object::set_property(ctx, realm_constructor, "List", list_constructor, attributes); Object::set_property(ctx, realm_constructor, "Results", results_constructor, attributes); + Object::set_property(ctx, realm_constructor, "Object", realm_object_constructor, attributes); return realm_constructor; } @@ -469,7 +471,7 @@ void Realm::write(ContextType ctx, ObjectType this_object, size_t argc, const } catch (std::exception &e) { realm->cancel_transaction(); - throw e; + throw; } realm->commit_transaction(); diff --git a/src/js_realm_object.hpp b/src/js_realm_object.hpp index c5df2226..d9c0bbde 100644 --- a/src/js_realm_object.hpp +++ b/src/js_realm_object.hpp @@ -46,6 +46,8 @@ class RealmObject { static void get_property(ContextType, ObjectType, const String &, ReturnValue &); static bool set_property(ContextType, ObjectType, const String &, ValueType); static std::vector get_property_names(ContextType, ObjectType); + + static void is_valid(ContextType, ObjectType, size_t, const ValueType [], ReturnValue &); }; template @@ -59,8 +61,17 @@ struct RealmObjectClass : ClassDefinition { wrap, wrap, }; + + MethodMap const methods = { + {"isValid", wrap}, + }; }; +template +void RealmObject::is_valid(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + return_value.set(get_internal>(this_object)->is_valid()); +} + template typename T::Object RealmObject::create_instance(ContextType ctx, realm::Object &realm_object) { static String prototype_string = "prototype"; diff --git a/src/js_types.hpp b/src/js_types.hpp index 2147039e..0f2af00e 100644 --- a/src/js_types.hpp +++ b/src/js_types.hpp @@ -104,7 +104,7 @@ struct Value { static return_t validated_to_##type(ContextType ctx, const ValueType &value, const char *name = nullptr) { \ if (!is_##type(ctx, value)) { \ std::string prefix = name ? std::string("'") + name + "'" : "JS value"; \ - throw std::invalid_argument(prefix + " must be: " #type); \ + throw std::invalid_argument(prefix + " must be of type: " #type); \ } \ return to_##type(ctx, value); \ } diff --git a/src/object-store/src/object_accessor.hpp b/src/object-store/src/object_accessor.hpp index 61ef0a1d..3af25193 100644 --- a/src/object-store/src/object_accessor.hpp +++ b/src/object-store/src/object_accessor.hpp @@ -48,6 +48,8 @@ namespace realm { const ObjectSchema &get_object_schema() { return *m_object_schema; } Row row() { return m_row; } + bool is_valid() const { return m_row.is_attached(); } + private: SharedRealm m_realm; const ObjectSchema *m_object_schema; @@ -57,6 +59,8 @@ namespace realm { inline void set_property_value_impl(ContextType ctx, const Property &property, ValueType value, bool try_update); template inline ValueType get_property_value_impl(ContextType ctx, const Property &property); + + inline void verify_attached(); }; // @@ -109,6 +113,13 @@ namespace realm { static Mixed to_mixed(ContextType ctx, ValueType &val) { throw std::runtime_error("'Any' type is unsupported"); } }; + class InvalidatedObjectException : public std::runtime_error + { + public: + InvalidatedObjectException(const std::string object_type, const std::string message) : std::runtime_error(message), object_type(object_type) {} + const std::string object_type; + }; + class InvalidPropertyException : public std::runtime_error { public: @@ -161,6 +172,8 @@ namespace realm { { using Accessor = NativeAccessor; + verify_attached(); + if (!m_realm->is_in_transaction()) { throw MutationOutsideTransactionException("Can only set property values within a transaction."); } @@ -225,6 +238,8 @@ namespace realm { { using Accessor = NativeAccessor; + verify_attached(); + size_t column = property.table_column; if (property.is_nullable && m_row.is_null(column)) { return Accessor::null_value(ctx); @@ -324,6 +339,14 @@ namespace realm { } return object; } + + inline void Object::verify_attached() { + if (!m_row.is_attached()) { + throw InvalidatedObjectException(m_object_schema->name, + "Accessing object of type " + m_object_schema->name + " which has been deleted" + ); + } + } // // List implementation diff --git a/tests/js/object-tests.js b/tests/js/object-tests.js index addc067a..db41c5dd 100644 --- a/tests/js/object-tests.js +++ b/tests/js/object-tests.js @@ -431,5 +431,29 @@ module.exports = BaseTest.extend({ object.dataCol = [1]; }); }); - } + }, + + 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); + }); + }, + + testIsValid: function() { + var realm = new Realm({schema: [schemas.TestObject]}); + var obj; + realm.write(function() { + obj = realm.create('TestObject', {doubleCol: 1}); + TestCase.assertEqual(obj.isValid(), true); + realm.delete(obj); + TestCase.assertEqual(obj.isValid(), false); + }); + + TestCase.assertEqual(obj.isValid(), false); + TestCase.assertThrows(function() { + obj.doubleCol; + }); + }, }); diff --git a/tests/js/results-tests.js b/tests/js/results-tests.js index 3470a267..4c31eb28 100644 --- a/tests/js/results-tests.js +++ b/tests/js/results-tests.js @@ -50,6 +50,8 @@ module.exports = BaseTest.extend({ testResultsSubscript: function() { var realm = new Realm({schema: [schemas.PersonObject]}); + TestCase.assertEqual(realm.objects('PersonObject')[0], undefined); + realm.write(function() { realm.create('PersonObject', {name: 'name1', age: 1}); realm.create('PersonObject', {name: 'name2', age: 2});