Skip to main content

Creating A Relationship Between Two Models

Lesson Objectives

  1. Add Articles Array to Author Model
  2. Display Authors on New Article Page
  3. Creating a new Article Pushes a Copy Onto Author's Articles Array
  4. Display Author With Link on Article Show Page
  5. Display Author's Articles With Links On Author Show Page
  6. Deleting an Article Updates An Author's Articles List
  7. Updating an Article Updates An Author's Articles List
  8. Deleting an Author Deletes The Associated Articles
  9. 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>

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);
};
});