Merge pull request #395 from realm/al-is-valid

Add `realm.isValid()` to support checking for deleted objects
This commit is contained in:
Ari Lazier 2016-04-26 17:44:24 -07:00
commit 49fa4884ef
9 changed files with 88 additions and 13 deletions

View File

@ -5,9 +5,11 @@ x.x.x Release notes (yyyy-MM-dd)
### Enhancements ### Enhancements
* Support for queries comparing optional properties to `null` * Support for queries comparing optional properties to `null`
* `object.isValid()` has been added to enable checking if an object has been deleted
### Bugfixes ### 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) 0.11.1 Release notes (2016-3-29)
============================================================= =============================================================

View File

@ -23,7 +23,7 @@ import { keys, propTypes, objectTypes } from './constants';
import Collection, * as collections from './collections'; import Collection, * as collections from './collections';
import List, { createList } from './lists'; import List, { createList } from './lists';
import Results, { createResults } from './results'; import Results, { createResults } from './results';
import * as objects from './objects'; import RealmObject, { createObject, registerConstructors, typeForConstructor } from './objects';
import * as rpc from './rpc'; import * as rpc from './rpc';
import * as util from './util'; import * as util from './util';
@ -32,7 +32,7 @@ const listenersKey = Symbol();
rpc.registerTypeConverter(objectTypes.LIST, createList); rpc.registerTypeConverter(objectTypes.LIST, createList);
rpc.registerTypeConverter(objectTypes.RESULTS, createResults); rpc.registerTypeConverter(objectTypes.RESULTS, createResults);
rpc.registerTypeConverter(objectTypes.OBJECT, objects.create); rpc.registerTypeConverter(objectTypes.OBJECT, createObject);
export default class Realm { export default class Realm {
constructor(config) { constructor(config) {
@ -62,7 +62,7 @@ export default class Realm {
let realmId = rpc.createRealm(Array.from(arguments)); let realmId = rpc.createRealm(Array.from(arguments));
objects.registerConstructors(realmId, constructors); registerConstructors(realmId, constructors);
this[keys.id] = realmId; this[keys.id] = realmId;
this[keys.realm] = realmId; this[keys.realm] = realmId;
@ -79,7 +79,7 @@ export default class Realm {
create(type, ...args) { create(type, ...args) {
if (typeof type == 'function') { 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); let method = util.createMethod(objectTypes.REALM, 'create', true);
@ -88,7 +88,7 @@ export default class Realm {
objects(type, ...args) { objects(type, ...args) {
if (typeof type == 'function') { if (typeof type == 'function') {
type = objects.typeForConstructor(this[keys.realm], type); type = typeForConstructor(this[keys.realm], type);
} }
let method = util.createMethod(objectTypes.REALM, 'objects'); let method = util.createMethod(objectTypes.REALM, 'objects');
@ -169,6 +169,9 @@ Object.defineProperties(Realm, {
Results: { Results: {
value: Results, value: Results,
}, },
Object: {
value: RealmObject,
},
defaultPath: { defaultPath: {
get: util.getterForProperty('defaultPath'), get: util.getterForProperty('defaultPath'),
set: util.setterForProperty('defaultPath'), set: util.setterForProperty('defaultPath'),

View File

@ -18,15 +18,23 @@
'use strict'; 'use strict';
import { keys } from './constants'; import { keys, objectTypes } from './constants';
import { getterForProperty, setterForProperty } from './util'; import { getterForProperty, setterForProperty, createMethods } from './util';
const registeredConstructors = {}; 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 schema = info.schema;
let constructor = (registeredConstructors[realmId] || {})[schema.name]; 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.realm] = realmId;
object[keys.id] = info.id; object[keys.id] = info.id;

View File

@ -231,11 +231,13 @@ inline typename T::Function Realm<T>::create_constructor(ContextType ctx) {
FunctionType collection_constructor = ObjectWrap<T, CollectionClass<T>>::create_constructor(ctx); FunctionType collection_constructor = ObjectWrap<T, CollectionClass<T>>::create_constructor(ctx);
FunctionType list_constructor = ObjectWrap<T, ListClass<T>>::create_constructor(ctx); FunctionType list_constructor = ObjectWrap<T, ListClass<T>>::create_constructor(ctx);
FunctionType results_constructor = ObjectWrap<T, ResultsClass<T>>::create_constructor(ctx); FunctionType results_constructor = ObjectWrap<T, ResultsClass<T>>::create_constructor(ctx);
FunctionType realm_object_constructor = ObjectWrap<T, RealmObjectClass<T>>::create_constructor(ctx);
PropertyAttributes attributes = PropertyAttributes(ReadOnly | DontEnum | DontDelete); PropertyAttributes attributes = PropertyAttributes(ReadOnly | DontEnum | DontDelete);
Object::set_property(ctx, realm_constructor, "Collection", collection_constructor, attributes); 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, "List", list_constructor, attributes);
Object::set_property(ctx, realm_constructor, "Results", results_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; return realm_constructor;
} }
@ -469,7 +471,7 @@ void Realm<T>::write(ContextType ctx, ObjectType this_object, size_t argc, const
} }
catch (std::exception &e) { catch (std::exception &e) {
realm->cancel_transaction(); realm->cancel_transaction();
throw e; throw;
} }
realm->commit_transaction(); realm->commit_transaction();

View File

@ -46,6 +46,8 @@ class RealmObject {
static void get_property(ContextType, ObjectType, const String &, ReturnValue &); static void get_property(ContextType, ObjectType, const String &, ReturnValue &);
static bool set_property(ContextType, ObjectType, const String &, ValueType); static bool set_property(ContextType, ObjectType, const String &, ValueType);
static std::vector<String> get_property_names(ContextType, ObjectType); static std::vector<String> get_property_names(ContextType, ObjectType);
static void is_valid(ContextType, ObjectType, size_t, const ValueType [], ReturnValue &);
}; };
template<typename T> template<typename T>
@ -59,8 +61,17 @@ struct RealmObjectClass : ClassDefinition<T, realm::Object> {
wrap<RealmObject::set_property>, wrap<RealmObject::set_property>,
wrap<RealmObject::get_property_names>, wrap<RealmObject::get_property_names>,
}; };
MethodMap<T> const methods = {
{"isValid", wrap<RealmObject::is_valid>},
};
}; };
template<typename T>
void RealmObject<T>::is_valid(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
return_value.set(get_internal<T, RealmObjectClass<T>>(this_object)->is_valid());
}
template<typename T> template<typename T>
typename T::Object RealmObject<T>::create_instance(ContextType ctx, realm::Object &realm_object) { typename T::Object RealmObject<T>::create_instance(ContextType ctx, realm::Object &realm_object) {
static String prototype_string = "prototype"; static String prototype_string = "prototype";

View File

@ -104,7 +104,7 @@ struct Value {
static return_t validated_to_##type(ContextType ctx, const ValueType &value, const char *name = nullptr) { \ static return_t validated_to_##type(ContextType ctx, const ValueType &value, const char *name = nullptr) { \
if (!is_##type(ctx, value)) { \ if (!is_##type(ctx, value)) { \
std::string prefix = name ? std::string("'") + name + "'" : "JS 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); \ return to_##type(ctx, value); \
} }

View File

@ -48,6 +48,8 @@ namespace realm {
const ObjectSchema &get_object_schema() { return *m_object_schema; } const ObjectSchema &get_object_schema() { return *m_object_schema; }
Row row() { return m_row; } Row row() { return m_row; }
bool is_valid() const { return m_row.is_attached(); }
private: private:
SharedRealm m_realm; SharedRealm m_realm;
const ObjectSchema *m_object_schema; 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); inline void set_property_value_impl(ContextType ctx, const Property &property, ValueType value, bool try_update);
template<typename ValueType, typename ContextType> template<typename ValueType, typename ContextType>
inline ValueType get_property_value_impl(ContextType ctx, const Property &property); 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"); } 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 class InvalidPropertyException : public std::runtime_error
{ {
public: public:
@ -161,6 +172,8 @@ namespace realm {
{ {
using Accessor = NativeAccessor<ValueType, ContextType>; using Accessor = NativeAccessor<ValueType, ContextType>;
verify_attached();
if (!m_realm->is_in_transaction()) { if (!m_realm->is_in_transaction()) {
throw MutationOutsideTransactionException("Can only set property values within a transaction."); throw MutationOutsideTransactionException("Can only set property values within a transaction.");
} }
@ -225,6 +238,8 @@ namespace realm {
{ {
using Accessor = NativeAccessor<ValueType, ContextType>; using Accessor = NativeAccessor<ValueType, ContextType>;
verify_attached();
size_t column = property.table_column; size_t column = property.table_column;
if (property.is_nullable && m_row.is_null(column)) { if (property.is_nullable && m_row.is_null(column)) {
return Accessor::null_value(ctx); return Accessor::null_value(ctx);
@ -325,6 +340,14 @@ namespace realm {
return object; 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 // List implementation
// //

View File

@ -431,5 +431,29 @@ module.exports = BaseTest.extend({
object.dataCol = [1]; 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;
});
},
}); });

View File

@ -50,6 +50,8 @@ module.exports = BaseTest.extend({
testResultsSubscript: function() { testResultsSubscript: function() {
var realm = new Realm({schema: [schemas.PersonObject]}); var realm = new Realm({schema: [schemas.PersonObject]});
TestCase.assertEqual(realm.objects('PersonObject')[0], undefined);
realm.write(function() { realm.write(function() {
realm.create('PersonObject', {name: 'name1', age: 1}); realm.create('PersonObject', {name: 'name1', age: 1});
realm.create('PersonObject', {name: 'name2', age: 2}); realm.create('PersonObject', {name: 'name2', age: 2});