I've looked through a bunch of other SO posts and have found different ways to do this, so I'm wondering which is most preferred. I'm teaching this to students, so I want to give them best practices.
If I have the following BlogPost object (Simplified):
var BlogPostSchema = new mongoose.Schema({
body: String,
comments: [String]
});
and I want to add a new comment to the array of comments for this blog, I can think of at least 3 main ways to accomplish this:
1) Push the comment to the blog object in Angular and submit a PUT request to the /blogs/:blogID endpoint, updating the whole blog object with the new comment included.
2) Submit a POST request to a /blogs/:blogID/comments endpoint where the request body is just the new comment, find the blog, push the comment to the array in vanilla js, and save it:
BlogPost.findById(req.params.blogID, function(err, blogPost) {
blogPost.comments.push(req.body);
blogPost.save(function(err) {
if (err) return res.status(500).send(err);
res.send(blogPost);
});
});
OR
3) Submit the POST to a /blogs/:blogID/comments endpoint with the request body of the new comment, then use MongoDB's $push or $addToSet to add the commend to the array of comments:
BlogPost.findByIdAndUpdate(
req.params.blogID,
{$push: {comments: req.body}},
{safe: true, new: true},
function(err, blogPost) {
if (err) return res.status(500).send(err);
res.send(blogPost);
});
});
I did find this stackoverflow post where the answerer talks about option 2 vs. option 3 and basically says to use option 2 whenever you can, which does seem simpler to me. (And I usually try to avoid methods that stop me from being able to use hooks and other mongoose goodies.)
What do you think? Any advice?
From application point of view, point 3 is better. The reason I think are.
The query itself specifies what we are trying to achieve. it's
easily readable.
save function is a wild card, so we don't know what it's going to change.
if you fetch the document and manipulate it and then call save it, there is outside but real chance that you might mess up some
other field of the document in process of manipulation
unintentionally, not the case with point 3.
In case of addToSet,basically the previous point is more visible.
Think about the concurrency, if multiple calls comes with different comment for same blog and you are trying option 2, there
is a chance that you might override the changes which were done in
between you fetched the document and when you are saving it. Option
3 is better in that sense.
Performance wise they both do the same thing, so there might not be much or any visible difference. But option 3 is bit safer and cleaner.
Related
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.
I am using node.js with bookshelf as an ORM. I am a serious novice with this technology.
I have a situation where I have several columns in a database table. For the sake of this question, these columns shall be named 'sold_by_id', 'signed_off_by_id' and 'lead_developer_id', and are all columns that will reference a User table with an ID.
In other words, different User's in the system would at any point be associated with three different roles, not necessarily uniquely.
Going forward, I would need to be able to retrieve information in such ways as:
let soldByLastName = JobTicket.soldBy.get('last_name');
I've tried searching around and reading the documentation but I'm still very uncertain about how to achieve this. Obviously the below doesn't work and I'm aware that the second parameter is meant for the target table, but it illustrates the concept of what I'm trying to achieve.
// JobTicket.js
soldBy: function() {
return this.belongsTo(User, 'sold_by_id');
},
signedOffBy: function() {
return this.belongsTo(User, 'signed_off_by_id');
},
leadDeveloper: function() {
return this.belongsTo(User, 'lead_developer_id');
}
Obviously I would need a corresponding set of methods in User.js
I'm not sure where to start, can anyone point me in the right direction??
Or am I just a total idiot? ^_^
Your definitions look right. For using them it will be something like:
new JobTicket({ id: 33 })
.fetch({ withRelated: [ 'soldBy', 'signedOffBy' ] })
.then(jobTicket => {
console.log(jobTicket.related('soldBy').get('last_name');
});
Besides that I would recommend you to use the Registry plugin for referencing other models. That eases the pains of referencing models not yet loaded.
I am relatively new to Meteor, and I'm trying to create a web store for my sister-in-law that takes data from her existing Etsy store and puts a custom skin on it. I've defined all of my Meteor.methods to retrieve the data, and I've proofed the data with a series of console.log statements... So, the data is there, but it won't render on the screen. Here is an example of some of the code on the server side:
Meteor.methods({
...
'getShopSections': function() {
this.unblock();
var URL = baseURL + "/sections?api_key="+apiKey;
var response = Meteor.http.get(URL).data.results;
return response;
}
...
});
This method returns an array of Object. A sample bit of JSON string from one of the returned Objects from the array:
{
active_listing_count: 20,
rank: 2,
shop_section_id: 1******0,
title: "Example Title",
user_id: 2******7
}
After fetching this data without a hitch, I was ready to make the call from the client side, and I tried and failed in several different ways before a Google search landed me at this tutorial here: https://dzone.com/articles/integrating-external-apis-your
On the client side, I have a nav.js file with the following bit of code, adapted from the above tutorial:
Template.nav.rendered = function() {
Meteor.call('getShopSections', function(err, res) {
Session.set('sections', res);
return res;
});
};
Template.nav.helpers({
category: function() {
var sections = Session.get('sections');
return sections;
}
});
And a sample call from inside my nav.html template...
<ul>
{{#each category}}
<li>{{category.title}}</li>
{{/each}}
</ul>
So, there's a few things going on here that I'm unsure of. First and foremost, the DOM is not rendering any of the category.title String despite showing the appropriate number of li placeholders. Secondly, before I followed the above tutorial, I didn't define a Session variable. Considering that the list of shop categories should remain static once the template is loaded, I didn't think it was necessary from what I understand about Session variables... but for some reason this was the difference between the template displaying a single empty <li> tag versus a number of empty <li>'s equal to category.length --- so, even though I can't comprehend why the Session variable is needed in this instance, it did bring me one perceived step closer to my goal... I have tried a number of console.log statements on the client side, and I am 100% sure the data is defined and available, but when I check the source code in my Developer Tools window, the DOM just shows a number of empty li brackets.
Can any Meteor gurus explain why 1) the DOM is not rendering any of the titles, and 2) if the Session variable indeed necessary? Please let me know if more information is needed, and I'll be very happy to provide it. Thanks!
You set the data context when you use #each, so simply use:
<li>{{title}}</li>
If a Session is the right type of reactive variable to use here or not is hard to determine without knowing what you are doing but my rough guess is that a Mini Mongo collection may be better suited for what it appears you are doing.
To get you started on deciding the correct type of reactive variable to use for this head over to the full Meteor documentation and investigate: collections, sessions, and reactive vars.
Edit: To step back and clarify a bit, a Template helper is called a reactive computation. Reactive computations inside of helpers will only execute if they are used in their respective templates AND if you use a reactive variable inside of the computation. There are multiple types of reactive variable, each with their own attributes. Your code likely didn't work at all before you used Session because you were not using a reactive variable.
See below.
https://jsfiddle.net/z30y983n/1/
First, the script gets 4 items from Github API and renders a list. If you submit 'NEXT' button, the script re-draws the list after re-sends Get request.
GET request.
repo.Repo.getList = function (api) {
return m.request({
method: "GET",
url: api,
type: repo.Repo,
extract: repo.linkHeader.setLinkHeader,
initialValue: []
})
.then(function (data) {
// bad solution.
return repo.vm.list(repo.vm.list().concat(data));
});
};
Concat Array.
repo.vm.api(links['next'])
return m('button', {onclick: repo.vm.add}, 'NEXT');
It works just as expected. But, It is bad solution, aren't you? I think
this concat process should be completed in View-Model (repo.vm).
Is there any good method? Or is this all right?
It's not ideal to modify things in the view method. Consider the view a template, that should only display state, since redraws can happen quite rapidly. Instead you should let the controller do the request, and modify state when the request is finished. Then the view will be displayed.
Code-wise, I think you're a bit deep into "too much structure". There are viewmodels, a LinkHeader prototype, and the program flow jumps all over the place. The mental model is quite simple, so keep it that way instead of getting into patterns that will only make things abstract and complicated.
Here's my take on it: https://jsfiddle.net/ciscoheat/akwdqhpx/
The parser is the same, but after that I've tried to keep the code compressed and local, so you can look at a part of the code and understand it. Here's a very good article about locality and cohesion. I've also changed a few names to keep closer to the mental model.
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); })
});