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
* 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)
=============================================================

View File

@ -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'),

View File

@ -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;

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 list_constructor = ObjectWrap<T, ListClass<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);
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<T>::write(ContextType ctx, ObjectType this_object, size_t argc, const
}
catch (std::exception &e) {
realm->cancel_transaction();
throw e;
throw;
}
realm->commit_transaction();

View File

@ -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<String> get_property_names(ContextType, ObjectType);
static void is_valid(ContextType, ObjectType, size_t, const ValueType [], ReturnValue &);
};
template<typename T>
@ -59,8 +61,17 @@ struct RealmObjectClass : ClassDefinition<T, realm::Object> {
wrap<RealmObject::set_property>,
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>
typename T::Object RealmObject<T>::create_instance(ContextType ctx, realm::Object &realm_object) {
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) { \
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); \
}

View File

@ -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<typename ValueType, typename ContextType>
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<ValueType, ContextType>;
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<ValueType, ContextType>;
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

View File

@ -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;
});
},
});

View File

@ -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});