React to schema changes (#1831)
* Adding schema change listener (#1825).
This commit is contained in:
parent
16bea5f42b
commit
58671dd59e
|
@ -6,7 +6,7 @@ x.x.x Release notes (xxxx-xx-xx)
|
|||
* None.
|
||||
|
||||
### Enhancements
|
||||
* None
|
||||
* Added schema change listener to `Realm.addListener()` (#1825).
|
||||
|
||||
### Bug fixes
|
||||
* Fix `Realm.open()` to work without passing a config.
|
||||
|
|
|
@ -209,8 +209,8 @@ class Realm {
|
|||
/**
|
||||
* Add a listener `callback` for the specified event `name`.
|
||||
* @param {string} name - The name of event that should cause the callback to be called.
|
||||
* _Currently, only the "change" event supported_.
|
||||
* @param {callback(Realm, string)} callback - Function to be called when the event occurs.
|
||||
* _Currently, only the "change" and "schema" are events supported_.
|
||||
* @param {callback(Realm, string)|callback(Realm, string, Schema)} callback - Function to be called when a change event occurs.
|
||||
* Each callback will only be called once per event, regardless of the number of times
|
||||
* it was added.
|
||||
* @throws {Error} If an invalid event `name` is supplied, or if `callback` is not a function.
|
||||
|
@ -220,8 +220,8 @@ class Realm {
|
|||
/**
|
||||
* Remove the listener `callback` for the specfied event `name`.
|
||||
* @param {string} name - The event name.
|
||||
* _Currently, only the "change" event supported_.
|
||||
* @param {callback(Realm, string)} callback - Function that was previously added as a
|
||||
* _Currently, only the "change" and "schema" are events supported_.
|
||||
* @param {callback(Realm, string)|callback(Realm, string, Schema)} callback - Function that was previously added as a
|
||||
* listener for this event through the {@link Realm#addListener addListener} method.
|
||||
* @throws {Error} If an invalid event `name` is supplied, or if `callback` is not a function.
|
||||
*/
|
||||
|
@ -230,7 +230,7 @@ class Realm {
|
|||
/**
|
||||
* Remove all event listeners (restricted to the event `name`, if provided).
|
||||
* @param {string} [name] - The name of the event whose listeners should be removed.
|
||||
* _Currently, only the "change" event supported_.
|
||||
* _Currently, only the "change" and "schema" are events supported_.
|
||||
* @throws {Error} When invalid event `name` is supplied
|
||||
*/
|
||||
removeAllListeners(name) {}
|
||||
|
|
|
@ -669,6 +669,7 @@ declare class Realm {
|
|||
* @returns void
|
||||
*/
|
||||
addListener(name: string, callback: (sender: Realm, event: 'change') => void): void;
|
||||
addListener(name: string, callback: (sender: Realm, event: 'schema', schema: Realm.ObjectSchema[]) => void): void;
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
|
@ -676,6 +677,7 @@ declare class Realm {
|
|||
* @returns void
|
||||
*/
|
||||
removeListener(name: string, callback: (sender: Realm, event: 'change') => void): void;
|
||||
removeListener(name: string, callback: (sender: Realm, event: 'schema', schema: Realm.ObjectSchema[]) => void): void;
|
||||
|
||||
/**
|
||||
* @param {string} name?
|
||||
|
|
|
@ -81,6 +81,10 @@ class RealmDelegate : public BindingContext {
|
|||
notify("change");
|
||||
}
|
||||
|
||||
virtual void schema_did_change(realm::Schema const& schema) {
|
||||
schema_notify("schema", schema);
|
||||
}
|
||||
|
||||
RealmDelegate(std::weak_ptr<realm::Realm> realm, GlobalContextType ctx) : m_context(ctx), m_realm(realm) {}
|
||||
|
||||
~RealmDelegate() {
|
||||
|
@ -88,6 +92,7 @@ class RealmDelegate : public BindingContext {
|
|||
m_defaults.clear();
|
||||
m_constructors.clear();
|
||||
m_notifications.clear();
|
||||
m_schema_notifications.clear();
|
||||
}
|
||||
|
||||
void add_notification(FunctionType notification) {
|
||||
|
@ -98,6 +103,7 @@ class RealmDelegate : public BindingContext {
|
|||
}
|
||||
m_notifications.emplace_back(m_context, notification);
|
||||
}
|
||||
|
||||
void remove_notification(FunctionType notification) {
|
||||
for (auto iter = m_notifications.begin(); iter != m_notifications.end(); ++iter) {
|
||||
if (*iter == notification) {
|
||||
|
@ -106,16 +112,42 @@ class RealmDelegate : public BindingContext {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void remove_all_notifications() {
|
||||
m_notifications.clear();
|
||||
}
|
||||
|
||||
void add_schema_notification(FunctionType notification) {
|
||||
SharedRealm realm = m_realm.lock();
|
||||
realm->read_group(); // to get the schema change handler going
|
||||
for (auto &handler : m_schema_notifications) {
|
||||
if (handler == notification) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_schema_notifications.emplace_back(m_context, notification);
|
||||
}
|
||||
|
||||
void remove_schema_notification(FunctionType notification) {
|
||||
for (auto iter = m_schema_notifications.begin(); iter != m_schema_notifications.end(); ++iter) {
|
||||
if (*iter == notification) {
|
||||
m_schema_notifications.erase(iter);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void remove_all_schema_notifications() {
|
||||
m_schema_notifications.clear();
|
||||
}
|
||||
|
||||
ObjectDefaultsMap m_defaults;
|
||||
ConstructorMap m_constructors;
|
||||
|
||||
private:
|
||||
Protected<GlobalContextType> m_context;
|
||||
std::list<Protected<FunctionType>> m_notifications;
|
||||
std::list<Protected<FunctionType>> m_schema_notifications;
|
||||
std::weak_ptr<realm::Realm> m_realm;
|
||||
|
||||
void notify(const char *notification_name) {
|
||||
|
@ -135,6 +167,24 @@ class RealmDelegate : public BindingContext {
|
|||
}
|
||||
}
|
||||
|
||||
void schema_notify(const char *notification_name, realm::Schema const& schema) {
|
||||
HANDLESCOPE
|
||||
|
||||
SharedRealm realm = m_realm.lock();
|
||||
if (!realm) {
|
||||
throw std::runtime_error("Realm no longer exists");
|
||||
}
|
||||
|
||||
ObjectType realm_object = create_object<T, RealmClass<T>>(m_context, new SharedRealm(realm));
|
||||
ObjectType schema_object = Schema<T>::object_for_schema(m_context, schema);
|
||||
ValueType arguments[] = {realm_object, Value::from_string(m_context, notification_name), schema_object};
|
||||
|
||||
std::list<Protected<FunctionType>> notifications_copy(m_schema_notifications);
|
||||
for (auto &callback : notifications_copy) {
|
||||
Function<T>::callback(m_context, callback, realm_object, 3, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
friend class RealmClass<T>;
|
||||
};
|
||||
|
||||
|
@ -288,10 +338,10 @@ public:
|
|||
|
||||
static std::string validated_notification_name(ContextType ctx, const ValueType &value) {
|
||||
std::string name = Value::validated_to_string(ctx, value, "notification name");
|
||||
if (name != "change") {
|
||||
throw std::runtime_error("Only the 'change' notification name is supported.");
|
||||
if (name == "change" || name == "schema") {
|
||||
return name;
|
||||
}
|
||||
return name;
|
||||
throw std::runtime_error("Only the 'change' and 'schema' notification names are supported.");
|
||||
}
|
||||
|
||||
static const ObjectSchema& validated_object_schema_for_value(ContextType ctx, const SharedRealm &realm, const ValueType &value) {
|
||||
|
@ -954,36 +1004,52 @@ template<typename T>
|
|||
void RealmClass<T>::add_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(2);
|
||||
|
||||
validated_notification_name(ctx, args[0]);
|
||||
auto name = validated_notification_name(ctx, args[0]);
|
||||
auto callback = Value::validated_to_function(ctx, args[1]);
|
||||
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
realm->verify_open();
|
||||
get_delegate<T>(realm.get())->add_notification(callback);
|
||||
if (name == "change") {
|
||||
get_delegate<T>(realm.get())->add_notification(callback);
|
||||
}
|
||||
else {
|
||||
get_delegate<T>(realm.get())->add_schema_notification(callback);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::remove_listener(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(2);
|
||||
|
||||
validated_notification_name(ctx, args[0]);
|
||||
auto name = validated_notification_name(ctx, args[0]);
|
||||
auto callback = Value::validated_to_function(ctx, args[1]);
|
||||
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
realm->verify_open();
|
||||
get_delegate<T>(realm.get())->remove_notification(callback);
|
||||
if (name == "change") {
|
||||
get_delegate<T>(realm.get())->remove_notification(callback);
|
||||
}
|
||||
else {
|
||||
get_delegate<T>(realm.get())->remove_schema_notification(callback);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void RealmClass<T>::remove_all_listeners(ContextType ctx, ObjectType this_object, Arguments args, ReturnValue &return_value) {
|
||||
args.validate_maximum(1);
|
||||
std::string name = "change";
|
||||
if (args.count) {
|
||||
validated_notification_name(ctx, args[0]);
|
||||
name = validated_notification_name(ctx, args[0]);
|
||||
}
|
||||
|
||||
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
|
||||
realm->verify_open();
|
||||
get_delegate<T>(realm.get())->remove_all_notifications();
|
||||
if (name == "change") {
|
||||
get_delegate<T>(realm.get())->remove_all_notifications();
|
||||
}
|
||||
else {
|
||||
get_delegate<T>(realm.get())->remove_all_schema_notifications();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
|
|
|
@ -803,7 +803,7 @@ module.exports = {
|
|||
TestCase.assertEqual(secondNotificationCount, 1);
|
||||
|
||||
TestCase.assertThrowsContaining(() => realm.addListener('invalid', () => {}),
|
||||
"Only the 'change' notification name is supported.");
|
||||
"Only the 'change' and 'schema' notification names are supported.");
|
||||
|
||||
realm.addListener('change', () => {
|
||||
throw new Error('expected error message');
|
||||
|
@ -1220,6 +1220,43 @@ module.exports = {
|
|||
}, 'The Realm file format must be allowed to be upgraded in order to proceed.');
|
||||
},
|
||||
|
||||
|
||||
// FIXME: We need to test adding a property also calls the listener
|
||||
testSchemaUpdatesNewClass: function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let realm1 = new Realm({ _cache: false });
|
||||
TestCase.assertTrue(realm1.empty);
|
||||
TestCase.assertEqual(realm1.schema.length, 0); // empty schema
|
||||
realm1.addListener('schema', (realm, event, schema) => {
|
||||
TestCase.assertEqual(event, 'schema');
|
||||
TestCase.assertEqual(schema.length, 1);
|
||||
TestCase.assertEqual(realm.schema.length, 1);
|
||||
TestCase.assertEqual(schema[0].name, 'TestObject');
|
||||
TestCase.assertEqual(realm1.schema.length, 1);
|
||||
TestCase.assertEqual(realm.schema[0].name, 'TestObject');
|
||||
});
|
||||
|
||||
const schema = [{
|
||||
name: 'TestObject',
|
||||
properties: {
|
||||
prop0: 'string',
|
||||
}
|
||||
}];
|
||||
|
||||
let realm2 = new Realm({ schema: schema, _cache: false });
|
||||
TestCase.assertEqual(realm1.schema.length, 0); // not yet updated
|
||||
TestCase.assertEqual(realm2.schema.length, 1);
|
||||
|
||||
// give some time to let advance_read to complete
|
||||
// in real world, a Realm will not be closed just after its
|
||||
// schema has been updated
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
// FIXME: reanble test
|
||||
/*
|
||||
testWriteCopyTo: function() {
|
||||
|
|
Loading…
Reference in New Issue