Creating A Relationship Between Two Models
Lesson Objectives
- Add Articles Array to Author Model
- Display Authors on New Article Page
- Creating a new Article Pushes a Copy Onto Author's Articles Array
- Display Author With Link on Article Show Page
- Display Author's Articles With Links On Author Show Page
- Deleting an Article Updates An Author's Articles List
- Updating an Article Updates An Author's Articles List
- Deleting an Author Deletes The Associated Articles
- Change Author When Editing an Article
Add Articles Array to Author Model
models/authors.js
const mongoose = require("mongoose");
const Article = require("./articles.js");
const authorSchema = mongoose.Schema({
name: String,
articles: [Article.schema],
});
const Author = mongoose.model("Author", authorSchema);
module.exports = Author;
Display Authors on New Article Page
Require the Author model in controllers/articles.js:
const Author = require("../models/authors.js");
Find all Authors When Rendering New Page:
router.get("/new", async (req, res) => {
try {
const allAuthors = await Author.find({});
res.render("articles/new.ejs", {
authors: allAuthors,
});
} catch (error) {
console.log(error);
};
});
Create A Select Element in views/articles/new.ejs:
<form action="/articles" method="post">
<select name="authorId">
<% for(let i = 0; i < authors.length; i++) { %>
<option value="<%=authors[i]._id%>"><%=authors[i].name%></option>
<% } %>
</select><br/>
<input type="text" name="title" /><br/>
<textarea name="body"></textarea><br/>
<input type="submit" value="Publish Article"/>
</form>
Creating a new Article Pushes a Copy Onto Author's Articles Array
controllers/articles.js:
router.post("/", async (req, res) => {
try {
const foundAuthor = await Author.findById(req.body.authorId);
const createdArticle = await Article.create(req.body);
await foundAuthor.articles.push(createdArticle);
foundAuthor.save();
res.redirect("/articles");
} catch (error) {
console.log(error);
};
});
NOTE: req.body.authorId is ignored when creating Article due to Article Schema
Display Author With Link on Article Show Page
controllers/articles.js:
router.get("/:id", async (req, res) => {
try {
const foundArticle = await Article.findById(req.params.id);
const foundAuthor = await Author.findOne({ "articles._id": req.params.id });
res.render("articles/show.ejs", {
author: foundAuthor,
article: foundArticle,
});
} catch (error) {
console.log(error);
};
});
views/articles/show.ejs:
<h1><%=article.title%></h1>
<small>by: <a href="/authors/<%=author._id%>"><%=author.name%></a></small>
Display Author's Articles With Links On Author Show Page
views/authors/show.ejs:
<section>
<h2>Articles Written By This Author:</h2>
<ul>
<% for(let i = 0; i < author.articles.length; i++){ %>
<li><a href="/articles/<%=author.articles[i]._id%>"><%=author.articles[i].title%></a></li>
<% } %>
</ul>
</section>
Deleting an Article Updates An Author's Articles List
controllers/articles.js
router.delete("/:id", async (req, res) => {
try {
const foundArticle = await Article.findByIdAndRemove(req.params.id);
const foundAuthor = await Author.findOne({ "articles._id": req.params.id });
await foundAuthor.articles.id(req.params.id).remove()
foundAuthor.save()
res.redirect("/articles");
} catch (error) {
console.log(error);
};
});
Updating an Article Updates An Author's Articles List
controllers/articles.js
router.put("/:id", async (req, res) => {
try {
const updatedArticle = await Article.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true });
const foundAuthor = await Author.findOne({ "articles._id": req.params.id });
await foundAuthor.articles.id(req.params.id).remove();
foundAuthor.save();
res.redirect("/articles/" + req.params.id);
} catch (error) {
console.log(error);
};
});
Deleting an Author Deletes The Associated Articles
controllers/authors.js
const Article = require("../models/articles.js");
//...farther down the file
router.delete("/:id", async (req, res) => {
try {
const foundAuthor = await Author.findByIdAndRemove(req.params.id);
const articleIds = [];
for (let i = 0; i < foundAuthor.articles.length; i++) {
articleIds.push(foundAuthor.articles[i]._id);
}
Article.remove(
{
_id: {
$in: articleIds,
},
});
res.redirect("/authors");
} catch (error) {
console.log(error);
};
});
Change Author When Editing an Article
controllers/articles.js
router.get("/:id/edit", async (req, res) => {
try {
const foundArticle = await Article.findById(req.params.id);
const allAuthors = await Author.find({});
const foundArticleAuthor = Author.findOne({ "articles._id": req.params.id });
res.render("articles/edit.ejs",{
article: foundArticle,
authors: allAuthors,
articleAuthor: foundArticleAuthor,
});
} catch (error) {
console.log(error);
};
});
views/articles/edit.ejs
<form action="/articles/<%=article._id%>?_method=PUT" method="post">
<select name="authorId">
<% for(let i = 0; i < authors.length; i++) { %>
<option
value="<%=authors[i]._id%>"
<% if(authors[i]._id.toString() === articleAuthor._id.toString()){ %>
selected
<% } %>
>
<%=authors[i].name%>
</option>
<% } %>
</select><br/>
<input type="text" name="title" value="<%=article.title%>"/><br/>
<textarea name="body"><%=article.body%></textarea><br/>
<input type="submit" value="Update Article"/>
</form>
NOTE: to compare ObjectIds, which are objects, you must first convert them to Strings (e.g. articleAuthor._id.toString())
Update the PUT route in controllers/articles.js
router.put("/:id", async (req, res) => {
try {
const updatedArticle = await Article.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true });
const foundAuthor = await Author.findOne({ "articles._id": req.params.id });
const newAuthor = await Author.findById(req.body.authorId);
await newAuthor.articles.push(updatedArticle);
newAuthor.save()
res.redirect("/articles/" + req.params.id);
if (foundAuthor._id.toString() !== req.body.authorId) {
await foundAuthor.articles.id(req.params.id).remove();
} else {
await foundAuthor.articles.id(req.params.id).remove();
await foundAuthor.articles.push(updatedArticle);
foundAuthor.save()
res.redirect("/articles/" + req.params.id);
}
} catch (error) {
console.log(error);
};
});