I am using realm-js to store data for a user on their device with React Native and there is a point in the workflow where I would like to copy all of the data in the local realm into a synced realm (to persist on ROS). I am running into an issue where in our schemas we have created pseudo relationships by adding a property referencing one object into another object and vice-versa and in doing so we have created circular data structures.
This has actually worked fine in the app but now we are attempting to copy the data from the local realm into the synced realm and it crashes, seemingly, because of the circular references.
For example the schemas look something like this.
class Person extends Realm.Object {}
Person.schema = {
name: 'Person',
properties: {
firstName: {
type: 'string',
optional: true,
},
staffAccount: {
type: 'StaffAccount'
},
},
};
class StaffAccount extends Realm.Object {}
StaffAccount.schema = {
name: 'StaffAccount',
properties: {
id: {
type: 'string',
},
people: {
type: 'list',
objectType: 'Person',
},
},
};
In this example when creating a Person you would define a staffAccount property and that staff account property would have a people property which has a list of the people with that staff account and in that list there would be the person initial person in the Person schema.
Is there any way of getting around this problem when copying data from one realm to another?
Take a look at this code that takes a local .realm file and copies it into a remote synced Realm. The code could be simplified since it looks like you already know the schema - this code dynamically loads the schema. Hope it helps
// Copy local realm to ROS
const Realm = require('realm');
// UPDATE THESE
const realm_server = 'localhost:9080';
const source_realm_path = './localRealm.realm'; // path on disk
const target_realm_path = '/syncRealm'; // path on server
function copyObject(obj, objSchema, targetRealm) {
const copy = {};
for (var key in objSchema.properties) {
const prop = objSchema.properties[key];
if (prop.type == 'list') {
const propObjSchema = targetRealm.schema.find((s) => s.name == prop.objectType)
copy[key] = obj[key].map((obj) => copyObject(obj, propObjSchema, targetRealm))
}
else if (prop.type == 'object') {
const propObjSchema = targetRealm.schema.find((s) => s.name == prop.objectType)
copy[key] = obj[key] ? copyObject(obj[key], propObjSchema, targetRealm) : obj[key];
}
else {
copy[key] = obj[key];
}
}
return copy;
}
function getMatchingObjectInOtherRealm(sourceObj, source_realm, target_realm, class_name) {
const allObjects = source_realm.objects(class_name);
const ndx = allObjects.indexOf(sourceObj);
// Get object on same position in target realm
return target_realm.objects(class_name)[ndx];
}
function addLinksToObject(sourceObj, targetObj, objSchema, source_realm, target_realm) {
for (var key in objSchema.properties) {
const prop = objSchema.properties[key];
if (prop.hasOwnProperty('objectType')) {
if (prop['type'] == "list") {
var targetList = targetObj[key];
sourceObj[key].forEach((linkedObj) => {
const obj = getMatchingObjectInOtherRealm(linkedObj, source_realm, target_realm, prop.objectType);
targetList.push(obj);
});
}
else {
// Find the position of the linked object
const linkedObj = sourceObj[key];
if (linkedObj === null) {
continue;
}
// Set link to object on same position in target realm
targetObj[key] = getMatchingObjectInOtherRealm(linkedObj, source_realm, target_realm, prop.objectType);
}
}
}
}
function copyRealm(user, local_realm_path, remote_realm_url) {
// Open the local realm
const source_realm = new Realm({path: local_realm_path});
// Create the new realm (with same schema as the source)
const target_realm = new Realm({
sync: {
user: user,
url: remote_realm_url,
},
schema: require('./realmmodels')
});
// Copy all objects but ignore links for now
target_realm.schema.forEach((objSchema) => {
console.log("copying objects:", objSchema['name']);
const allObjects = source_realm.objects(objSchema['name']);
target_realm.write(() =>
allObjects.forEach((obj) => {
// Add this object to the target realm
target_realm.create(objSchema.name, copyObject(obj, objSchema, target_realm), true)
}));
});
}
const remote_realm_url = "realm://" + realm_server + target_realm_path;
copyRealm(Realm.Sync.User.adminUser("ADMIN_TOKEN"),
source_realm_path, remote_realm_url);
console.log("done");
Related
I have this code in js bin:
var validator = {
set (target, key, value) {
console.log(target);
console.log(key);
console.log(value);
if(isObject(target[key])){
}
return true
}
}
var person = {
firstName: "alfred",
lastName: "john",
inner: {
salary: 8250,
Proffesion: ".NET Developer"
}
}
var proxy = new Proxy(person, validator)
proxy.inner.salary = 'foo'
if i do proxy.inner.salary = 555; it does not work.
However if i do proxy.firstName = "Anne", then it works great.
I do not understand why it does not work Recursively.
http://jsbin.com/dinerotiwe/edit?html,js,console
You can add a get trap and return a new proxy with validator as a handler:
var validator = {
get(target, key) {
if (typeof target[key] === 'object' && target[key] !== null) {
return new Proxy(target[key], validator)
} else {
return target[key];
}
},
set (target, key, value) {
console.log(target);
console.log(key);
console.log(value);
return true
}
}
var person = {
firstName: "alfred",
lastName: "john",
inner: {
salary: 8250,
Proffesion: ".NET Developer"
}
}
var proxy = new Proxy(person, validator)
proxy.inner.salary = 'foo'
A slight modification on the example by Michał Perłakowski with the benefit of this approach being that the nested proxy is only created once rather than every time a value is accessed.
If the property of the proxy being accessed is an object or array, the value of the property is replaced with another proxy. The isProxy property in the getter is used to detect whether the currently accessed object is a proxy or not. You may want to change the name of isProxy to avoid naming collisions with properties of stored objects.
Note: the nested proxy is defined in the getter rather than the setter so it is only created if the data is actually used somewhere. This may or may not suit your use-case.
const handler = {
get(target, key) {
if (key == 'isProxy')
return true;
const prop = target[key];
// return if property not found
if (typeof prop == 'undefined')
return;
// set value as proxy if object
if (!prop.isProxy && typeof prop === 'object')
target[key] = new Proxy(prop, handler);
return target[key];
},
set(target, key, value) {
console.log('Setting', target, `.${key} to equal`, value);
// todo : call callback
target[key] = value;
return true;
}
};
const test = {
string: "data",
number: 231321,
object: {
string: "data",
number: 32434
},
array: [
1, 2, 3, 4, 5
],
};
const proxy = new Proxy(test, handler);
console.log(proxy);
console.log(proxy.string); // "data"
proxy.string = "Hello";
console.log(proxy.string); // "Hello"
console.log(proxy.object); // { "string": "data", "number": 32434 }
proxy.object.string = "World";
console.log(proxy.object.string); // "World"
I published a library on GitHub that does this as well. It will also report to a callback function what modifications have taken place along with their full path.
Michal's answer is good, but it creates a new Proxy every time a nested object is accessed. Depending on your usage, that could lead to a very large memory overhead.
I have also created a library type function for observing updates on deeply nested proxy objects (I created it for use as a one-way bound data model). Compared to Elliot's library it's slightly easier to understand at < 100 lines. Moreover, I think Elliot's worry about new Proxy objects being made is a premature optimisation, so I kept that feature to make it simpler to reason about the function of the code.
observable-model.js
let ObservableModel = (function () {
/*
* observableValidation: This is a validation handler for the observable model construct.
* It allows objects to be created with deeply nested object hierarchies, each of which
* is a proxy implementing the observable validator. It uses markers to track the path an update to the object takes
* <path> is an array of values representing the breadcrumb trail of object properties up until the final get/set action
* <rootTarget> the earliest property in this <path> which contained an observers array *
*/
let observableValidation = {
get(target, prop) {
this.updateMarkers(target, prop);
if (target[prop] && typeof target[prop] === 'object') {
target[prop] = new Proxy(target[prop], observableValidation);
return new Proxy(target[prop], observableValidation);
} else {
return target[prop];
}
},
set(target, prop, value) {
this.updateMarkers(target, prop);
// user is attempting to update an entire observable field
// so maintain the observers array
target[prop] = this.path.length === 1 && prop !== 'length'
? Object.assign(value, { observers: target[prop].observers })
: value;
// don't send events on observer changes / magic length changes
if(!this.path.includes('observers') && prop !== 'length') {
this.rootTarget.observers.forEach(o => o.onEvent(this.path, value));
}
// reset the markers
this.rootTarget = undefined;
this.path.length = 0;
return true;
},
updateMarkers(target, prop) {
this.path.push(prop);
this.rootTarget = this.path.length === 1 && prop !== 'length'
? target[prop]
: target;
},
path: [],
set rootTarget(target) {
if(typeof target === 'undefined') {
this._rootTarget = undefined;
}
else if(!this._rootTarget && target.hasOwnProperty('observers')) {
this._rootTarget = Object.assign({}, target);
}
},
get rootTarget() {
return this._rootTarget;
}
};
/*
* create: Creates an object with keys governed by the fields array
* The value at each key is an object with an observers array
*/
function create(fields) {
let observableModel = {};
fields.forEach(f => observableModel[f] = { observers: [] });
return new Proxy(observableModel, observableValidation);
}
return {create: create};
})();
It's then trivial to create an observable model and register observers:
app.js
// give the create function a list of fields to convert into observables
let model = ObservableModel.create([
'profile',
'availableGames'
]);
// define the observer handler. it must have an onEvent function
// to handle events sent by the model
let profileObserver = {
onEvent(field, newValue) {
console.log(
'handling profile event: \n\tfield: %s\n\tnewValue: %s',
JSON.stringify(field),
JSON.stringify(newValue));
}
};
// register the observer on the profile field of the model
model.profile.observers.push(profileObserver);
// make a change to profile - the observer prints:
// handling profile event:
// field: ["profile"]
// newValue: {"name":{"first":"foo","last":"bar"},"observers":[{}
// ]}
model.profile = {name: {first: 'foo', last: 'bar'}};
// make a change to available games - no listeners are registered, so all
// it does is change the model, nothing else
model.availableGames['1234'] = {players: []};
Hope this is useful!
I wrote a function based on Michał Perłakowski code. I added access to the path of property in the set/get functions. Also, I added types.
const createHander = <T>(path: string[] = []) => ({
get: (target: T, key: keyof T): any => {
if (key == 'isProxy') return true;
if (typeof target[key] === 'object' && target[key] != null)
return new Proxy(
target[key],
createHander<any>([...path, key as string])
);
return target[key];
},
set: (target: T, key: keyof T, value: any) => {
console.log(`Setting ${[...path, key]} to: `, value);
target[key] = value;
return true;
}
});
const proxy = new Proxy(obj ,createHander<ObjectType>());
I have an array of object called TourStop, which is two level nested. The types are as below.
TourStops = TourStop[]
TourStop = {
suggestions?: StoreSuggestion[]
id: Uuid
}
StoreSuggestion= {
id: Uuid
spaceSuggestions: SpaceSuggestion[]
}
SpaceSuggestion = {
id: Uuid
title: string
}
My goal is to remove a particular StoreSuggestion from a particular TourStop
I have written the following code(uses immutability-helper and hooks)
const [tourStopsArray, setTourStopsArray] = useState(tourStops)
// function to remove the store suggestion
const removeSuggestionFromTourStop = (index: number, tourStopId: Uuid) => {
// find the particular tourStop
const targetTourStop = tourStopsArray.find(arr => arr.id === tourStopId)
// Update the storeSuggestion in that tourstop
const filteredStoreSuggestion = targetTourStop?.suggestions?.filter(sugg => sugg.id !== index)
if (targetTourStop) {
// create a new TourStop with the updated storeSuggestion
const updatedTargetTourStop: TourStopType = {
...targetTourStop,
suggestions: filteredStoreSuggestion,
}
const targetIndex = tourStopsArray.findIndex(
tourStop => tourStop.id == updatedTargetTourStop.id,
)
// Find it by index and remove it
setTourStopsArray(
update(tourStopsArray, {
$splice: [[targetIndex, 1]],
}),
)
// add the new TourStop
setTourStopsArray(
update(tourStopsArray, {
$push: [updatedTargetTourStop],
}),
)
}
}
The push action works correctly. However the splice action doesn't work for some reason. What am I doing wrong?
I'm trying to create a component that will render elements inside VueJs virtual dom with a Vuex state.
The problem is I get this error but I don't understand why and how to fix it
Avoid using observed data object as vnode data: {"class":"btn btn-default"}
Always create fresh vnode data objects in each render!
Inside my Vuex state I store and object where I define the elements properties
{
type: 'a',
config: {
class: 'btn btn-default',
},
nestedElements: [
{
type: 'span',
value: 'test',
},
{
type: 'i',
},
],
},
My components code look like
methods: {
iterateThroughObject(object, createElement, isNestedElement = false) {
const generatedElement = [],
nestedElements = [];
let parentElementConfig = {};
for (const entry of object) {
let nodeConfig = {};
if (typeof entry.config !== 'undefined' && isNestedElement) {
nodeConfig = entry.config;
} else if (typeof entry.config !== 'undefined') {
parentElementConfig = entry.config;
}
if (entry.nestedElements) {
nestedElements.push(this.iterateThroughObject(entry.nestedElements, createElement, true));
}
if (!isNestedElement) {
nodeConfig = parentElementConfig;
}
generatedElement.push(createElement(
entry.type,
nodeConfig === {} ? entry.value : nodeConfig,
nestedElements
));
}
if (isNestedElement) {
return generatedElement;
}
return createElement('ul', generatedElement);
},
},
render(createElement) {
const barToolsElements = this.$store.state.titleBar.barToolsElements;
if (barToolsElements) {
return this.iterateThroughObject(barToolsElements, createElement);
}
return false;
},
The error is produced when I try to pass inside my last generatedElement.push() definition.
Because entry.value is {"class":"btn btn-default"}.
I don't understand why it tell me to recreate a fresh Vnode object while this value is used only once.
Did I miss or misunderstand something?
Might be because you're passing references to objects in your store's state, which might lead inadvertently to their mutation. Try creating deep clones of these objects when you pass them around, like for example ..
nodeConfig = JSON.parse(JSON.stringify(entry.config));
parentElementConfig = JSON.parse(JSON.stringify(entry.config));
nodeConfig === {} ? JSON.parse(JSON.stringify(entry.value)) : nodeConfig,
I'm not really sure how to explain so I will start with the output.
I need to return this:
{
replies:
[
{ type: 'text', content: 'one' }
{ type: 'text', content: 'two' }
{ type: 'text', content: 'three' }
],
conversation: {
memory
}
}
And I wanted to return that through in-line statement.
So I would like to call something like:
reply.addText('one').addText('two').addText('three').addConversation(memory)
Note that addText can be called infinite times while addConversation can be called only one time. Also conversation is optional, in that case, if conversation is absent the conversation object should not appear in the output.
To create a custom structured object use a constructor, say Reply.
To call instance methods on the return value of method calls, return the instance object from the method.
Choices to prevent multiple additions of conversation objects include throwing an error (as below) or perhaps logging a warning and simply not add additional objects after a first call to addConversation.
Write the code to implement the requirements.
For example using vanilla javascript:
function Reply() {
this.replies = [];
}
Reply.prototype.addText = function( content) {
this.replies.push( {type: "text", content: content});
return this;
}
Reply.prototype.addConversation = function( value) {
if( this.conversation) {
//throw new Error("Only one conversation allowed");
}
this.conversation = {conversation: value};
return this;
};
Reply.prototype.conversation = null;
// demo
var reply = new Reply();
reply.addText( "one").addText("two").addConversation("memory?");
console.log( JSON.stringify( reply, undefined," "));
(The console.log uses JSON stringify to avoid listing inherited methods)
A possible implementation is to create a builder as follows:
function create() {
const replies = []; // store all replies in this array
let conversation; // store the memory here
let hasAddConversationBeenCalled = false; // a state to check if addConversation was ever called
this.addText = function(content) {
// add a new reply to the array
replies.push({
type: 'text',
content
});
return this; // return the builder
};
this.addConversation = function(memory) {
if (!hasAddConversationBeenCalled) { // check if this was called before
// if not set the memory
conversation = {
memory
};
hasAddConversationBeenCalled = true; // set that the memory has been set
}
return this; // return the builder
}
this.build = function() {
const reply = {
replies
};
if (conversation) { // only if converstation was set
reply.conversation = conversation; // add it to the final reply object
}
return reply; // finally return the built respnse
}
return this; // return the new builder
}
You can then use it as follows:
const builder = create();
const reply = builder.addText('one').addText('two').addText('three').addConversation({}).build();
Here is a link to a codepen to play around with.
If you specifically want to add assemble this via multiple function calls, then the builder pattern is your best bet, as vader said in their comment.
However, if the goal is to simply create shorthand for concisely building these objects, it can be done using a function that takes the list of text as an array.
const buildObject = (textArray, memory) => {
return Object.assign(
{},
{
replies: textArray.map(x => {
return {
type: 'text',
value: x
}
})
},
memory ? {conversation: memory} : null
)
}
var memory = { };
//with memory
console.log(buildObject(['one', 'two', 'three'], memory ))
//without memory
console.log(buildObject(['one', 'two', 'three']));
Fiddle example: http://jsfiddle.net/ucxkd4g3/
I'm learning about prototypal inheritance and I've read numerous articles and watched multiple videos on the subject. It's starting to make sense now.
The following code example, which can be found at this JSFiddle, takes an array of objects and for each object, it sets a key in a Map object, with an object as its value.
The value object has two methods, get and set, which get and set values to another map object from the local scope of the template factory that returns the object.
My understanding is if we used this example to hypothetically generate 100k objects in the databaseMap, each object would have its own set and get methods, using unnecessary amounts of memory? My understanding is this is where I should be using prototypal inheritance?
So my question is, how do I move the set and get methods to say the scope of the init function and set them to the prototype of the object returned from template, so that when called, they set and get on the storeMap Map, from within the scope of the template function that generated the object?
Or, am I not understanding this correctly?
const config = [
{
id: 'obj1',
value: 'value1',
},
{
id: 'obj2',
value: 'value2',
},
{
id: 'obj3',
value: 'value3',
},
{
id: 'obj4',
value: 'value4',
},
]
function init() {
const databaseMap = new Map()
function template(storeConfig) {
const { id } = storeConfig
const storeMap = new Map()
return {
id,
set(key, value) {
console.log(`Setting data to store ${id}.`)
// Do future work here
return storeMap.set(key, value)
},
get(key) {
// Do future work here
return storeMap.get(key)
},
}
}
config.forEach(x => {
const store = template({ id: x.id })
databaseMap.set(x.id, store)
})
return databaseMap
}
const db = init()
const getStore = db.get('obj4')
getStore.set('testing1', 'testing1')
console.log('GET STORE')
console.log(getStore)
console.log('GET TESTING 1')
console.log(getStore.get('testing1'))
Have you considered just using a single object with get and set defined on it? Instead of creating a new object your can create a new Template() that — then you can take advantage of the way this works in javascript to allow it to work for all instances while avoiding having to capture a new closure for each function.
For example:
const config = [{id: 'obj1', value: 'value1'}, {id: 'obj2', value: 'value2'},{id: 'obj3',value: 'value3',},{id: 'obj4',value: 'value4',}]
function init() {
const databaseMap = new Map()
// a single proto object
const protoObj = {
set(key, value) {
console.log(`Setting data to store ${this.id}.`)
// Do future work here
return this.storeMap.set(key, value)
},
get(key) {
return this.storeMap.get(key)
}
}
function Template(storeConfig) {
this.id = storeConfig.id
this.storeMap = new Map()
}
// use the object for the prototype
Template.prototype = protoObj
config.forEach(x => {
const store = new Template({ id: x.id })
databaseMap.set(x.id, store)
})
return databaseMap
}
const db = init()
const getStore = db.get('obj4')
getStore.set('aTest', 'testing1')
console.log('GET STORE')
console.log(getStore)
console.log('GET aTest')
console.log(getStore.get('aTest'))
Of course you can also define the function directly on the prototype:
Template.prototype.set = function (key, value) {
return this.storeMap.set(key, value)
}
Template.prototype.get = function (key, value) {
return this.storeMap.get(key, value)
}
EDIT based on comment
You can define the functions on their own in such a way that they use this to access the object's properties. Then you can just add a reference from them to the object. Like:
const config = [{id: 'obj1', value: 'value1'}, {id: 'obj2', value: 'value2'},{id: 'obj3',value: 'value3',},{id: 'obj4',value: 'value4',}]
function init() {
const databaseMap = new Map()
function set(key, value) {
console.log(`Setting data to store ${this.id}.`)
// Do future work here
return this.storeMap.set(key, value)
}
function get(key) {
// Do future work here
return this.storeMap.get(key)
}
function template(storeConfig) {
return {
id: storeConfig.id,
storeMap:new Map(),
set:set,
get:get
}
}
config.forEach(x => {
const store = template({ id: x.id })
databaseMap.set(x.id, store)
})
return databaseMap
}
const db = init()
const getStore = db.get('obj4')
getStore.set('TestKey', 'testing1')
console.log('GET STORE')
console.log(getStore)
console.log('TestKey')
console.log(getStore.get('TestKey'))
ALTERNATIVE
const config = [{id: 'obj1', value: 'value1'}, {id: 'obj2', value: 'value2'},{id: 'obj3',value: 'value3',},{id: 'obj4',value: 'value4',}]
function init() {
const databaseMap = new Map()
// a single proto object
const protoObj = {
set(key, value) {
console.log(`Setting data to store ${this.id}.`)
// Do future work here
return this.storeMap.set(key, value)
},
get(key) {
return this.storeMap.get(key)
}
}
function template(storeConfig) {
const id = storeConfig.id
const storeMap = new Map()
return Object.assign(Object.create(protoObj), { id, storeMap })
}
config.forEach(x => {
const store = template({ id: x.id })
databaseMap.set(x.id, store)
})
return databaseMap
}
const db = init()
const getStore = db.get('obj4')
getStore.set('aTest', 'testing1')
console.log('GET STORE')
console.log(getStore)
console.log("PROTOTYPE OF getStore")
console.log(Object.getPrototypeOf(getStore))
console.log('GET aTest')
console.log(getStore.get('aTest'))