Backbone.js - "Syncing" models between collections - collections

I am used to Spine.js, but I want to try out Backbone.js.
In Spine, there are no collections. There are only models. In Spine, when I use a model in different places, Spine always keeps the models synced with each other when I update them. In Backbone, I find this not to be the case. I hope you can tell me what I am doing wrong!
I am trying to create 2 Articles collections which have 3 Article models inside them:
var Article = Backbone.Model.extend({});
var Articles = Backbone.Collection.extend({
model: Article,
localStorage: new Backbone.LocalStorage("Articles")
});
var articles1 = new Articles();
var articles2 = new Articles();
articles1.create({ id: 1, name: "Article 1" });
articles1.create({ id: 2, name: "Article 2" });
articles1.create({ id: 3, name: "Article 3" });
In Chrome I use the console and experiment a little:
articles1.size()
=> 3
articles2.size()
=> 0
articles2.fetch()
=> undefined
articles2.size()
=> 3
So far so good. The first Article in both collections is "Article 1":
a1 = articles1.first()
=> child
a2 = articles2.first()
=> child
a1.get("name")
=> "Article 1"
a2.get("name")
=> "Article 1"
But when I try to update the model in the first collection, it is not synced with the second collection:
a1.set({ name: "Article 1 - Updated" })
=> child
a1.save()
=> undefined
a1.get("name")
=> "Article 1 - Updated"
a2.get("name")
=> "Article 1"
I expected the model in the second collection to automatically update itself.
But I can see both article models have different cids:
a1.cid
=> "c0"
a2.cid
=> "c3"
So my question is... what do you do in Backbone? How do you keep models synced between collections?
I actually read this blog article: http://destroytoday.com/blog/reasons-for-spinejs/. He writes:
Dynamic Records
This one is just crazy black magic, but it solves a problem I faced
with Backbone.js. Let’s say you fetched a record in one view of the
app. Then you fetch and update that same record in a different view.
In Spine.js, both records will update. You don’t have to worry about
keeping them in sync. The moment I read about this, a single tear
rolled down my cheek.
I have really tried to search about this but without luck.
I hope you can point me in the right direction on how to do this the Backbone way!

You have 2 basic options:
Make sure both collections contain references to the exact same instances of models. Not just 2 objects with the same ID, but actually the same in-memory single model instance for that record.
Use the events bindings to try to keep either the collections or the models inside the collections in sync (add/remove/change/reset on collection1 in turn causes an add/remove/change/reset on collection2).
If you want the spine behavior, get #1 working. Perhaps use an instance cache as described here.

Related

When I get a document do I also get sub-collections? [duplicate]

Say I have this kind of structure
A (collection): {
a (doc): {
name:'Tim',
B (collection):{
b (doc): {
color:'blue'
}
}
}
}
where A and B are collections while a and b are documents.
Is there a way to get everything contained in a root document with one query?
If I query like this
db.collection("A").doc("a").get()
I just gets name:'Tim' field. What I want is to also get all B's documents.
I basically wish my query returns
{
user:'Tim',
B (collection):{
b (doc): {
color:'blue'
}
}
}
Is it possibly or do I really need to make multiple queries one for each collection :/ ?
Say I have a really deep nested tree of collections representing the user profile, my costs will raise like hell since each time I load a user profile I have a multiplier of read requests 1 x N where N is the depth of my tree :/.
If you are concerned about costs of each pull, you will need to structure your data according to your common view / pull needs, rather than what you might prefer for a perfect structure. If you need to pull these things together every time, Consider using "maps" for things that do not actually need to be sub-collections with documents.
In this example, "preferences" is a map.
{
user: "Tim",
preferences: {
color: "blue",
nickname: "Timster"
}
}
Each document is also limited in size to 1MB - so if you need to store something for this user that will scale and continue to grow, like log records, then it would make sense to break logs into a sub-collection that only gets pulled when you want it, making each log entry a separate document... And whether all logs for all users are stored in a separate parent collection, or a sub-collection of each user really depends on how you will be pulling logs and what will result in fast speeds, balanced against costs of pulls. If you're showing this user their last 10 searches, then a search-log would make good sense as a sub-collection. If you're pulling all search data for all users for analysis, then a separate parent level collection would make sense because you can pull all logs in 1 pull, to prevent the need to pull logs from each user separately.
You can also nest your pulls and promises together for convenience purposes.
// Get reference to all of the documents
console.log("Retrieving list of documents in collection");
let documents = collectionRef.limit(1).get()
.then(snapshot => {
snapshot.forEach(doc => {
console.log("Parent Document ID: ", doc.id);
let subCollectionDocs = collectionRef.doc(doc.id).collection("subCollection").get()
.then(snapshot => {
snapshot.forEach(doc => {
console.log("Sub Document ID: ", doc.id);
})
}).catch(err => {
console.log("Error getting sub-collection documents", err);
})
});
}).catch(err => {
console.log("Error getting documents", err);
});
As we know querying in Cloud Firestore is shallow by default. This type of query isn't supported, although it is something Google may consider in the future.
Adding to Matt R answer, if you're using babel or you can use async/await, you can get the same result with less code(no catch/then):
// Get reference to all of the documents
console.log("Retrieving list of documents in collection");
let documents = await collectionRef.get();
documents.forEach(async doc => {
console.log("Parent Document ID: ", doc.id);
let subCollectionDocs = await collectionRef.doc(doc.id).collection("subCollection").get()
subCollectionDocs.forEach(subCollectionDoc => {
subCollectionDoc.forEach(doc => {
console.log("Sub Document ID: ", doc.id);
})
});
});

How to get multiple objects in list at a point in time

I want to provide my users with an API (pointing to my server) that will fetch data from Firebase and return it to them. I want it to be a 'normal' point-in-time request (as opposed to streaming).
My data is 'boxes' within 'projects'. A user can query my API to get all boxes for a project.
My data is normalised, so I will look up the project and get a list of keys of boxes in that project, then go get each box record individually. Once I have them all, I will return the array to the user.
My question: what is the best way to do this?
Here's what I have, and it works. But it feels so hacky.
const projectId = req.params.projectId; // this is passed in by the user in their call to my server.
const boxes = [];
let totalBoxCount = 0;
let fetchedBoxCount = 0;
const projectBoxesRef = db
.child('data/projects')
.child(projectId)
.child('boxes'); // a list of box keys
function getBox(boxSnapshot) {
totalBoxCount++;
db
.child('data/boxes') // a list of box objects
.child(boxSnapshot.key())
.once('value')
.then(boxSnapshot => {
boxes.push(boxSnapshot.val());
fetchedBoxCount++;
if (fetchedBoxCount === totalBoxCount) {
res.json(boxes); // leap of faith that getBox() has been called for all boxes
}
});
}
projectBoxesRef.on('child_added', getBox);
// 'value' fires after all initial 'child_added' things are done
projectBoxesRef.once('value', () => {
projectBoxesRef.off('child_added', getBox);
});
There are some other questions/answers on separating the initial set of child_added objects, and they have influenced my current decision, but they don't seem to relate directly.
Thanks a truck-load for any help.
Update: JavaScript version of Jay's answer below:
db
.child('data/boxes')
.orderByChild(`projects/${projectId}`)
.equalTo(true)
.once('value', boxSnapshot => {
const result = // some parsing of response
res.json(result);
});
This may be too simple a solution but if you have projects, and each project has boxes
your projects node
projects
project_01
boxes
box_id_7: true
box_id_9: true
box_id_34: true
project_37
boxes
box_id_7: true
box_id_14: true
box_id_42: true
and the boxes node
boxes
box_id_7
name: "a 3D box"
shape: "Parallelepiped"
belongs_to_project
project_01: true
box_id_14
name: "I have unequal lenghts"
shape: "Rhumboid"
belongs_to_project
project_37: true
box_id_34
name: "Kinda like a box but with rectangles"
shape: "cuboid"
belongs_to_project
project_01: true
With that, just one (deep) query on the boxes node will load all of the boxes that belong to project_01, which in this case is box_id_7 and box_id_34.
You could go the the other way and since you know the box id for each project in the projects node, you could do a series of observers to load in each project via it's specific path /boxes/box_id_7 etc. I like the query better; faster and less bandwidth.
You could expand on this if a box can belong to multiple projects:
box_id_14
name: "I have unequal lenghts"
shape: "Rhumboid"
belongs_to_project
project_01: true
project_37: true
Now query on the boxes node for all boxes that are part of project_01 will get box_id_7, box_id_14 and box_id_34.
Edit:
Once that structure is in place, use a Deep Query to then get the boxes that belong to the project in question.
For example: suppose you want to craft a Firebase Deep Query to return all boxes where the box's belongs_to_project list contains an item with key "project_37"
boxesRef.queryOrderedByChild("belongs_to_project/project_37"
.queryEqualToValue(true)
.observeSingleEventOfType(.Value, withBlock: { snapshot in
print(snapshot)
})
OK I think I'm happy with my approach, using Promise.all to respond once all the individual 'queries' are returned:
I've changed my approach to use promises, then call Promise.all() to indicate that all the data is ready to send.
const projectId = req.params.projectId;
const boxPromises = [];
const projectBoxesRef = db
.child('data/projects')
.child(projectId)
.child('boxes');
function getBox(boxSnapshot) {
boxPromises.push(db
.child('data/boxes')
.child(boxSnapshot.key())
.once('value')
.then(boxSnapshot => boxSnapshot.val())
);
}
projectBoxesRef.on('child_added', getBox);
projectBoxesRef.once('value', () => {
projectBoxesRef.off('child_added', getBox);
Promise.all(boxPromises).then(boxes => res.json(boxes));
});

Idiomatic Redux: Dispatch more than one action

I have a Redux application that shows a list of posts. The state is more or less this:
{
posts: [
{id: 1, title: 'My Post'},
{id: 2, title: 'Also this one'},
{id: 3, title: 'Other Post'}
],
visible_post_ids: [1, 2]
}
Whenever I load some posts I add them to posts, then I replace the content of visible_post_ids.
This is my action creator for loading posts:
function loadPosts (filters) {
return function (dispatch, getState) {
return fetch(`/posts.json?filters=${filters}`)
.then((response) => response.json())
.then((posts) => {
dispatch(postsLoaded(posts)) // Will update `posts`
const postIds = posts.map((post) => post.id)
dispatch(updateVisiblePosts(postIds)) // Will update `visible_post_ids`
})
}
}
My question is: is it idiomatic to dispatch two (or more) events from a thunk? Or should I dispatch only one and handle it in various reducers?
Quick answer : there is no problem to dispatch two or more actions from a thunk, I think it's a good practice,especially if API Call response contains answers to two completely different concerns.
I think it depends what you are trying to represent, in your case you can have one action that represent an add of new posts and two different reducers can catch it and do different tasks with it.
But you can see that as two different actions (your example) and it's great too.
As Sergey L said, in your case with a unique action (for your case) it can create an interesting "dependency"
If you don't consider scenario when it is possible to postsLoaded without calling updateVisiblePosts, it is better to handle the state change just in postsLoaded.
Especially if you need them to be in sync. For example, if you need a grantee that visible_post_ids does not contains Ids from not existing/loaded posts. Besides it minimizes the updates as each dispatch will cause processing in React.
On the other hand, having these actions separate can make code more clear as you have very simple implementation for each action.

Optimizing Firebase data structure for two large paths

I think I've wrapped my head around denormalization as a primary method of optimization when storing data in Firebase as mentioned in question like this one and in this blog post but I'm getting stuck on one small detail.
Assuming I have two things in my domain, users and posts as in the blog article I mentioned, I might have 20,000 users and 20,000 posts. Because I denormalized everything like a good boy, root/users/posts exists as does root/posts. root/users/posts has a set of post keys with a value of true so that I can get all post keys for a user.
users: {
userid: {
name: 'johnny'
posts: {
-Kofijdjdlehh: true,
-Kd9isjwkjfdj: true
}
}
}
posts: {
-Kofijdjdlehh: {
title: 'My hot post',
content: 'this was my content',
postedOn: '3987298737'
},
-Kd9isjwkjfdj: {
title: 'My hot post',
content: 'this was my content',
postedOn: '3987298737'
}
}
Now, I want to list the title of all posts a user has posted. I don't want to load all 20,000 posts in order to get the title. I can only think of the following options:
Query the root/posts path in some way using the subset of keys that are set to true in the root/users/posts path (if this is possible, I haven't figured out how)
Store the title in the root/users/posts so that each entry in that path has the title duplicated looking like this:
posts: {
-Kofijdjdlehh: true
}
becomes
posts: {
-Kofijdjdlehh: {
title: 'This was my content'
}
}
This seems reasonable, but I haven't seen a single example of doing this, so I'm concerned that it's some anti-pattern.
Another way I haven't been able to find
I appreciate any pointers you might have or documentation I might have missed on this use case.
Either are valid solutions. #1 would be more work for whoever is reading the data, while #2 would be more work when data is saved. Also for #2, you'd have to handle updates to post's titles, though this would be pretty easy with the new multi-path updates.
To implement #1, you'd have you essentially do two queries. Here's a really basic solution which only handles adding posts. It listens for posts being added to the user, and then hooks up a listener to each post's title.
var usersPosts = {};
ref.child('users').child(userId).child('posts').on('child_added', function(idSnap) {
var id = idSnap.key();
ref.child('posts').child(id).child('title').on('value', function(titleSnap) {
usersPosts[id] = titleSnap.val();
});
});
For a third solution, you could use firebase-util, which automagically handles the above scenario and more. This code would essentially do the same as the code above, except it comes with the bonus of giving you one ref to handle.
new Firebase.util.NormalizedCollection(
[ref.child('users').child(userId).child("posts"), "posts"],
[ref.child("posts"), "post"]
).select(
{
key: "posts.$value",
alias: "x"
},
{
key: "post.title",
alias: "title"
}
).ref();
Note that the x value will always be true. It's necessary to select that because firebase-util requires you to select at least one field from each path.

Meteor - How do you exclude properties in a child collection from being published?

Imagine you have a collection similar to the following...
Tests = [
{
name: 'Some Test',
questions: [
{ question: 'Answer to life, the universe, and everything?', answer: '42' },
{ question: 'What is your favorite color?', answer: 'Blue' },
{ question: 'Airspeed velocity of unladen European Swallow?', answer: '24 mph' }
]
}
];
How do you publish the entire collection except for the answer property?
I understand you can do the following to omit properties from the publish...
Meteor.publish('tests', function() {
return Tests.find({}, {fields: {name:0}});
});
But I'm not sure how to omit a property from an array property.
Thanks!
It can't be done the way you want to do it. Meteor only supports field specifiers that are 1 level deep. You can sometimes get a sub-field specifier to work, but it's not reliable.
You can put your questions into their own collection with a testId field that links them back to the test, relational style. One question per document, and then you'll be able to specify that only the question field gets published.
Meteor.publish ('questions', function(testId) {
return Questions.find({testId: testId}, {fields: {question: 1}})
});
It's not ideal, but pretty painless compared to trying to find a workaround that allows your questions to live in the test document.
There might be a way to do this manually with a more involved publish. There's a similar question here with an answer that gets into it.

Resources