i'm trying to create a post-comment relationship​​ where the a user can write a post and others users can comment on the post.
I can show the posts but when in trying to do the join for displaying the comments that belongs to the post i cant..
below is my db schema
i was thinking that first i need to get the key from the posts node and then move to comments and somehow get the comments of each post..
and use it in *ngfor inside the ngfor of the post?
i was trying something like
findAllComments(){
this.db.list('posts', { preserveSnapshot: true})
.subscribe(snapshots=>{
snapshots.forEach(snapshot => {
return this.db.list(`comments/${snapshot.key}`)
});
});
}
but this returns void of course:
When I console.log:
findAllComments(){
this.db.list('/posts', { preserveSnapshot: true})
.subscribe(snapshots=>{
snapshots.forEach(snapshot => {
const kapa = this.db.list(`comments/${snapshot.key}`).do(console.log)
kapa.subscribe();
});
});
}
I get in console this
I'm not sure if my thinking on this is right.
I'm confused because I am new in angular and firebase.
You aren't returning a subset of posts (you're querying on all posts) so there's no need to have a join of any sort here. You can just query for all comments:
findAllComments(){
// {preserveSnapshot: true} is deprecated
return this.db.list('/comments').snapshotChanges();
}
Assuming you actually want to retrieve a subset of comments (not what your example depicts), you could do something like this:
this.replies = db.list('AngularFire/joins/messages').snapshotChanges().map(snapshots => {
console.log('snapshots', snapshots);
return snapshots.map(ss => {
return db.list(`AngularFire/joins/replies/${ss.key}`).valueChanges();
});
});
There is a complete working example of the latter here.
I guess in the first part, you are not subscribing to the comments list. As there is no subscription to the comments, the request to the get the list of comments from firebase will not be fired and hence you don't see any comments.
In the second part, as you are subscribing to the comments list, you are seeing them.
In cases like these, where you want to fetch something based on a previous request, you could use switch/concat/merge Maps. Hope this helps
Related
Let's say I have 2 models.
Post
Author
The Author looks like below:
{
authorId: 'ghr4334t',
fullName: 'This is a post!'
Nickname: 'Avola
}
The Post looks like below and will have a reference to the author like below:
{
postId: '12fdc24',
authorId: 'ghr4334t',
content: 'This is a post!'
}
Currently, when a user clicks on a post, in order to show all the relevant information, I load the data as follow:
getPost(postId).then(post=> {
getAuthor(listing.uid).then((document) => {
// update state so I have the post object and author object.
})
})
So the above, I load the post, then I load the author. Once I've loaded them both, I can finally construct a custom object:
const finalPost = {
author: { ...this.state.authorData },
post: { ...this.state.postData }
}
Naturally..If I have a couple more fields that reference other collections, there will be a nest of get and .then() calls like below:
getPost(postId).then(post=> {
getAuthor(listing.uid).then((document) => {
getSomethingElse(listing.uid).then((document) => {
getAnother(listing.uid).then((document) => {
// finally update state with everything.
})
})
})
})
Is there a more a better way to load related information together without having to stack .then() calls?
Unfortunately, there isn't a better way to achieve what you want, with queries directly. Queries in Firestore doesn't provide you with many options on how to query and return data, mainly, when you would need to do any kind of JOIN on them, to search via references, which makes the work not very easy. I believe the way you are doing is the best option you have for more now.
An alternative you can try is to have Subcollections, where you will have a subcollection of Author inside your collection Post. This way, you will only treat with the reference of the Post, since the Author will be within the document of each specific Post. This way, the queries would be more simple, looking like this below. Of course, this would require you to modify your database.
var messageRef = db.collection('Post').doc('Post1')
.collection('Author').doc('Author1');
In case you still think this is not enough, I would recommend you to raise a Feature Request at Google's System, where the Google Developers will be able to check if having a new way of getting data is possible to be implemented.
Let me know if the information clarified your doubts!
I have a fully functioning CRUD app that I'm building some additional functionality for. The new functionality allows users to make changes to a list of vendors. They can add new vendors, update them and delete them. The add and delete seem to be working just fine, but updating doesn't seem to be working even though it follows a similar method I use in the existing CRUD functionality elsewhere in the app. Here's my code:
// async function from AXIOS request
const { original, updatedVendor } = req.body;
let list = await Vendor.findOne({ id: 1 });
if (!list) return res.status(500).json({ msg: 'Vendors not found' });
let indexOfUpdate = list.vendors.findIndex(
(element) => element.id === original.id
);
list.vendors[indexOfUpdate].id = updatedVendor.id;
list.vendors[indexOfUpdate].name = updatedVendor.name;
const updated = await list.save();
res.json(updated);
The save() isn't updating the existing document on the DB side. I've console logged that the list.vendors array of objects is, indeed, being changed, but save() isn't doing the saving.
EDIT:
A note on the manner of using save, this format doesn't work either:
list.save().then(res.json(list));
EDIT 2:
To answer the questions about seeing the logs, I cannot post the full console.log(list.vendors) as it contains private information, however, I can confirm that the change made to the list is showing up when I run the following in the VendorSchema:
VendorSchema.post('save', function () {
console.log(util.inspect(this, { maxArrayLength: null }));
});
However, the save still isn't changing the DB side.
Since you are using nested objects, Mongoose will not be able to detect the changes made. You need to mark the modified as an object before the save
list.markModified('vendors');
I want to delete from an articles table using knex by article_id. This already exists in comments table as a foreign key.
How can I test that data has been deleted and how can I send that to the user.
I decided to approach this by writing a function to delete from both functions with a .then. Does this look like I am on the right lines?
exports.deleteArticleById = function (req, res, next) {
const { article_id } = req.params;
return connection('comments')
.where('comments.article_id', article_id)
.del()
.returning('*')
.then((deleted) => {
console.log(deleted);
return connection('articles')
.where('articles.article_id', article_id)
.del()
.returning('*');
})
.then((article) => {
console.log(article);
return res.status(204).send('article deleted');
})
.catch(err => next(err));
};
At the moment I am getting the correct data with the logs but I am getting a status 500 but I think I need to be trying to get a 204?
Any help would be much appreciated.
What you're trying to do is called a cascading deletion.
These are better (and almost always) handled at the database level instead of the application level.
It's the job of the DBMS to enforce this kind of referential integrity assuming you define your schema correctly so that entities are correctly linked together, via foreign keys.
In short, you should define your database schema as such that when you delete an Article, it's associated Comments also get deleted for you.
Here's how I would do it using knex.js migrations:
// Define Article.
db.schema.createTableIfNotExists('article', t => {
t.increments('article_id').primary()
t.text('content')
})
// Define Comment.
// Each Comment is associated with an Article (1 - many).
db.schema.createTableIfNotExists('comment', t => {
t.increments('comment_id').primary() // Add an autoincrement primary key (PK).
t.integer('article_id').unsigned() // Add a foreign key (FK)...
.references('article.article_id') // ...which references Article PK.
.onUpdate('CASCADE') // If Article PK is changed, update FK as well.
.onDelete('CASCADE') // If Article is deleted, delete Comment as well.
t.text('content')
})
So when you run this to delete an Article:
await db('article').where({ article_id: 1 }).del()
All Comments associated with that Article also get deleted, automatically.
Don't try to perform cascading deletions yourself by writing application code. The DBMS is specifically designed with intricate mechanisms to ensure that deletions always happen in a consistent manner; It's purpose is to handle these operations for you. it would be wasteful, complicated and quite error-prone to attempt to replicate this functionality yourself.
Fairly simple problem, just cant find the good/clean way to do this without making a call to another find
I've got my node app rigged up with Angular-Resource, and I'm just making some round-trip like data calls on new or changed data.
So ngResource making the $save() call to my /api/users/:id and such. And Node reacts to this call by creating or finding the user, making the updates, and saving them.
Whether through create() or save(), it returns the created record, and for right now, I use res.json(user) to spill the created/returned record for my Angular to handle populating my view with the updated information
Now, I know with Sequelizes find() and findAll() methods, I can use findAll({ include: [{ all: true }]}) or specify my models individually.
What I want to know is, what is the best way to get my records associations on save/create
and unfortunately, this just doesn't work:
models.User.create(newuser, {include:[{ all: true }]}).then(function(user) {
res.json(user);
});
Do I really have to perform another find() just to get my managed models associations?
To better illustrate the opted solution from RedactedProfile's comment, here's the code.
models.User
.create(newuser, {include:[{ all: true }]})
.then(user => {
user.reload().then(user => { res.json(user); })
});
I want to publish and subscribe subset of same collection based on different route. Here is what I have
In /server/publish.js
Meteor.publish("questions", function() {
return Questions.find({});
});
Meteor.publish("questionSummaryByUser", function(userId) {
var q = Questions.find({userId : userId});
return q;
});
In /client/main.js
Deps.autorun(function() {
Meteor.subscribe("questions");
});
Deps.autorun(function () {
Meteor.subscribe("questionSummaryByUser", Session.get("selectedUserId"));
});
I am using the router package (https://github.com/tmeasday/meteor-router). They way i want the app to work is when i go to "/questions" i want to list all the questions by all the users and when i visit "/users/:user_id/questions", I want to list questions only by specific user. For this I have setup the "/users/:user_id/questions" route to set the userid in "selectedUserId" session (which i am also using in "questionSummaryByUser" publish method.
However when i see the list of questions in "/users/:user_id/questions" I get all the questions irrespective of the user_id.
I read here that the collections are merged at client side, but still could not figure a solution for the above mentioned scenario.
Note that I just started with Meteor, so do not know in and outs of it.
Thanks in advance.
The good practice is to filter the collection data in the place where you use it, not rely of the subset you get by subscribe. That way you can be sure that the data you get is the same you want to display, even when you add further subscriptions to the same collection. Imagine if later you'd like to display, for example, a sidebar with top 10 questions from all users. Then you'd have to fetch those as well, and if you have a place when you display all subscribed data, you'll get a mess of every function.
So, in the template where you want to display user's questions, do
Template.mine.questions = function() {
return Questions.find({userId: Meteor.userId()});
};
Then you won't even need the separate questionSummaryByUser channel.
To filter data in the subscription, you have several options. Whichever you choose, keep in mind that subscription is not the place in which you choose the data to be displayed. This should always be filtered as above.
Option 1
Keep everything in a single parametrized channel.
Meteor.publish('questions', function(options) {
if(options.filterByUser) {
return Questions.find({userId: options.userId});
} else {
return Questions.find({});
}
});
Option 2
Make all channel return data only when it's needed.
Meteor.publish('allQuestions', function(necessary) {
if(!necessary) return [];
return Questions.find({});
});
Meteor.publish('questionSummaryByUser', function(userId) {
return Questions.find({userId : userId});
});
Option 3
Manually turn off subcriptions in the client. This is probably an overkill in this case, it requires some unnecessary work.
var allQuestionsHandle = Meteor.subscribe('allQuestions');
...
allQuestionsHandle.stop();