[Database] Lesson 6 – Writing Code to Manage a Simple Database (Continued)
- Tram Ho
By now we are quite familiar with the directory structure and the division of sub-procedure
tasks; Therefore, in this article we will quickly go through the remaining basic procedure
which are select
, update
, and delete
. Let’s start with the select
procedure. ## select More precisely select-by-id
– this is the most basic select
procedure and can be used as material for other more complex procedures'. Basically here we just need to do a few steps as follows - find the path to the records directory corresponding to the provided
id, then read the data from the
header.json and
content.md to load into
object results of
class Category.
procedure/category/select-by-id–async-throw.js const findRecordFolderPathById = require(“./sub-procedure/find-record-folder-path-by-id–async-throw”); const readRecordHeader = require(“./sub-procedure/read-record-header–async-throw”); const readRecordContent = require(“./sub-procedure/read-record-content–async-throw”); const Category = require(“../../type/Category”); module.exports = async ( in_recordId = “Infinity”, out_selected = new Category() ) => { try { /* find record’s folder path */ var found = { recordFolderPath: “…” }; await findRecordFolderPathById(in_recordId, found); /* read record’s header and content */ await readRecordHeader(found.recordFolderPath, out_selected); await readRecordContent(found.recordFolderPath, out_selected); } catch (error) { throw error; } }; // module.exports
Finding the path to the corresponding log directory is simply reading the names of all the directories and filtering out the matches. To collect the directory path of all the records we can reuse a previously written
sub-procedure. As for filtering to match, we will use a
for loop to match the
PP spirit instead of using the
.forEach iteration method of the array.
procedure/category/sub-procedure/find-record-folder-path-by-id–async-throw.js const readAllRecordFolderNames = require(“./read-all-record-folder-names–async- throw”); const path = require(“path”); module.exports = async ( in_recordId = “Infinity”, out_found = { recordFolderPath: “” } ) => { try { /* collect all records’ folder names */ var allRecordFolderNames = []; await readAllRecordFolderNames(allRecordFolderNames); /* search for matched folder name */ var matchedFolderName = “”; for (var folderName of allRecordFolderNames) { if (folderName.includes(in_recordId)) matchedFolderName = folderName; else /* do nothing */ ; } // for /* populate output path if found matched */ if (matchedFolderName == “”) /* do nothing */ ; else out_found.recordFolderPath = path.join( __dirname, “../../../data/category”, matchedFolderName ); // out_found } catch (error) { throw error; } }; // module.exports
Writing code to test
sub-procedure will be shortened. Here we will only write code to test the main
procedure. That way we will be able to keep the article content neater and not too long. After we have found the
path to the directory of the corresponding record, we proceed to read the data files and load the
object results. However, for operations that read content from files, we need to make sure that the result is in text with the
encoding code
utf-8.
procedure/category/sub-procedure/read-record-header–async-throw.js const Category = require(“../../../type/Category”); const fsPromises = require(“fs/promises”); const path = require(“path”); module.exports = async ( in_recordFolderPath = “”, out_record = new Category() ) => { try { var headerFilePath = path.join(in_recordFolderPath, “header.json”); var headerText = await fsPromises.readFile(headerFilePath, {encoding: “utf-8”}); var headerJSON = JSON.parse(headerText); var headerEntries = Object.entries(headerJSON); for (var entry of headerEntries) { var [key, value] = entry; out_record.set(key, value); } // for } catch (error) { throw error; } }; // module.exports
procedure/category/sub-procedure/read-record-content–async-throw.js const Category = require(“../../../type/Category” ); const fsPromises = require(“fs/promises”); const path = require(“path”); module.exports = async ( in_recordFolderPath = “”, out_record = new Category() ) => { try { var contentFilePath = path.join(in_recordFolderPath, “content.md”); var recordContent = await fsPromises.readFile(contentFilePath, {encoding: “utf-8”}); out_record.set(“markdown”, recordContent); } catch (error) { throw error; } }; // module.exports
And now we can write the code to test the
select-by-id procedure.
express-blog/test.js const Category = require(“./database/type/Category”); const databaseManager = require(“./database/manager”); void async function() { var selected = new Category(); await databaseManager.execute(“select-category-by-id”, “01”, selected); console.log(selected); }(); // void
CMD | Terminal
npm test Category(4) [Map] { ‘@id’ => ’01’, ‘name’ => ‘html’, ‘keywords’ => [ ‘basic tutorial’, ‘programming’ web’, ‘html’ ], ‘markdown’ => ‘Content of HTML category description single page…’ }
## update The use case of
update is when we open a post wrote previously posted to edit the content and then save. Now the information sent to the
server will be the complete set of
key/value pairs of an
object Category. The corresponding
route handler code will create an
object record of
class Category and call the
update procedure.
procedure/category/update–async-throw.js const writeRecordToDataFolder = require(“./sub-procedure/write-record-to-data-folder–async-throw”); const Category = require(“../../type/Category”); module.exports = async ( in_record = new Category(), out_updated = new Category() ) => { try { await writeRecordToDataFolder(in_record); Category.clone(in_record, out_updated); } catch (error) { throw error; } }; // module.exports
Compared to
insert, here we do not need to perform a new
id value initialization operation. However here we will need to modify
sub-procedure to write data to the records directory we defined earlier. The case now is that we already have the directory corresponding to the record to be updated and operating
fsPromises.mkdir(recordPath) in the code we have written below will give an error that
the directory already exists .
procedure/category/sub-procedure/write-record-to-data-folder–async-throw.js const path = require(“path”); const fsPromises = require(“fs/promises”); const Category = require(“../../../type/Category”); const writeRecordHeaderToFile = require(“./write-record-header-to-file–async-throw”); const writeRecordContentToFile = require(“./write-record-content-to-file–async-throw”); module.exports = async ( in_record = new Category() ) => { try { /* prepare path to record’s data folder */ var categoryFolderPath = path.join(__dirname, “../../../data/category “); var recordFolderName = “id-” + in_record.get(“@id”); var recordFolderPath = path.join(categoryFolderPath, recordFolderName); /* create folder for new record */ await fsPromises.mkdir(recordFolderPath); /* write record’s data to files */ await writeRecordHeaderToFile(in_record, recordFolderPath); await writeRecordContentToFile(in_record, recordFolderPath); } catch (error) { throw error; } }; // module.exports
The operation of writing data to files should certainly be no problem, because according to the NodeJS documentation provided, the operation
fsPromises.writeFile(filePath) will be automatic. replace the existing file with the new one. Therefore, we only need to add a condition to check if the records directory already exists before deciding to initialize the path for the new directory and run the command
fsPromises.mkdir(recordPath).
procedure/category/sub-procedure/write-record-to-data-folder–async-throw.js const Category = require(“../../../type/Category”); const findRecordFolderPathById = require(“./find-record-folder-path-by-id–async-throw”); const path = require(“path”); const fsPromises = require(“fs/promises”); const writeRecordHeaderToFile = require(“./write-record-header-to-file–async-throw”); const writeRecordContentToFile = require(“./write-record-content-to-file–async-throw”); module.exports = async ( in_record = new Category() ) => { try { var found = { recordFolderPath: “” }; await findRecordFolderPathById(in_record.get(“@id”), found); var recordExists = (found.recordFolderPath != “”); if (recordExists) { await writeRecordHeaderToFile(in_record, found.recordFolderPath); await writeRecordContentToFile(in_record, found.recordFolderPath); } else { /* prepare path to new record’s data folder */ var categoryFolderPath = path.join(__dirname, “../../../data/category”); var newRecordFolderName = “id-” + in_record.get(“@id”); var newRecordFolderPath = path.join(categoryFolderPath, newRecordFolderName); /* create folder for new record */ await fsPromises.mkdir(newRecordFolderPath); /* write new record’s data to files */ await writeRecordHeaderToFile(in_record, newRecordFolderPath); await writeRecordContentToFile(in_record, newRecordFolderPath); } // else } catch (error) { throw error; } }; // module.exports
Here I choose to write repeated calls to the
writeRecord… procedure for each instance of the record directory search result corresponding to
id. You can do it another way by creating a variable that stores the directory path first and changing the value of that variable with the
if … else … conditional block; Then use the variable containing that directory path to call procedures that write data to files only once at the end.
express-blog/test.js const Category = require(“./database/type/Category”); const databaseManager = require(“./database/manager”); void async function() { var selected = new Category(); var updated = new Category(); await databaseManager.execute(“select-category-by-id”, “01”, selected); selected.set(“name”, “html5”); await databaseManager.execute(“update-category”, selected, updated); console.log(updated); await databaseManager.execute(“select-category-by-id”, “02”, selected); selected.set(“name”, “css3”); await databaseManager.execute(“update-category”, selected, updated); console.log(updated); }(); // void
CMD | Terminal
npm test Category(4) [Map] { ‘@id’ => ’01’, ‘name’ => ‘html5’, ‘keywords’ => [ ‘basic tutorial’, ‘programming’ web’, ‘html’ ], ‘markdown’ => ‘Content of single page HTML category description…’ } Category(4) [Map] { ‘@id’ => ’02’, ‘name’ => ‘css3’, ‘keywords’ => [ ‘basic tutorial’, ‘web programming’, ‘css’ ], ‘markdown’ => ‘Content of single page CSS category description…’ }
## delete The use case of
delete is when we select the button to delete a category on the web interface that manages the article categories. Data sent to the
server will usually only need the
id element of the directory's identifier. What we need to do first is - check if there are any articles in this category
article; If so, we need to report an exception, if not, we can proceed to delete the corresponding
category record and return the information of the record that has been deleted from the
database.
procedure/category/delete-by-id–async-throw.js const selectArticlesByCategoryId = require(“../article/select-by-category-id–async-throw”); const removeRecordFromDatabase = require(“./sub-procedure/remove-record-from-database–async-throw”); const Category = require(“../../type/Category”); module.exports = async ( in_recordId = “Infinity”, out_deleted = new Category() ) => { try { var selectedArticles = []; await selectArticlesByCategoryId(in_recordId, selectedArticles); var theCategoryContainsSomeArticles = (selectedArticles.length != 0); if (theCategoryContainsSomeArticles) throw new Error(“There are articles in this category”); else await removeRecordFromDatabase(in_recordId, out_deleted); } catch (error) { throw error; } }; // module.exports
To check if any
article articles are in the specified category, we will search among all
article records to filter out the records with
category-id respectively. This is also the first
procedure that we created for the
procedure/article group. However, let's just assume that there are no posts in the category to be deleted and save this
procedure for later discussion.
procedure/article/select-by-category-id–async-throw.js module.exports = async ( in_categoryId = “Infinity”, out_matchedArticles = [] ) => { /* do nothing */ ; };
And in the event that no articles are in the specified category, the deletion of the data files and directories of this
category record will be delegated to a
sub-procedure named
removeRecord… as above.
procedure/category/sub-procedure/remove-record-from-database–async-throw.js const Category = require(“../../../type/Category”); const selectRecordById = require(“../select-by-id–async-throw”); const findRecordFolderPathById = require(“./find-record-folder-path-by-id–async-throw”); const fsPromises = require(“fs/promises”); const path = require(“path”); module.exports = async ( in_recordId = “Infinity”, out_deleted = new Category() ) => { try { await selectRecordById(in_recordId, out_deleted); var found = { recordFolderPath: “” }; await findRecordFolderPathById(in_recordId, found); found.headerFilePath = path.join(found.recordFolderPath, “header.json”); found.contentFilePath = path.join(found.recordFolderPath, “content.md”); await fsPromises.rm(found.headerFilePath); await fsPromises.rm(found.contentFilePath); await fsPromises.rmdir(found.recordFolderPath); } catch (error) { console.error(error); } }; // module.exports
Now let's just write
delete-by-id code in case the category contains no articles. The rest is left to the calculation later. :D
express-blog/test.js const Category = require(“./database/type/Category”); const databaseManager = require(“./database/manager”); void async function() { var deleted = new Category(); await databaseManager.execute(“delete-category-by-id”, “02”, deleted) console.log(deleted); }(); // void
CMD | Terminal
npm test Category(4) [Map] { ‘@id’ => ’02’, ‘name’ => ‘css3’, ‘keywords’ => [ ‘basic tutorial’, ‘programming’ web’, ‘css’ ], ‘markdown’ => ‘Content of CSS category description menu page…’ }
## End of post :D I'm sorry but maybe we'll did not bring in a procedure of the
procedure/article group for discussion in this article, in order to maintain the focus of the article around
procedure basic to the
category group. At the present time, I am sure that you can complete the code yourself to query all
article records and filter out those with the corresponding
category-id. We're already pretty used to working with directories and reading data from files, so the
procedures’ that arise due to your personal blog design needs obviously won’t be able to do either. Hard for you anymore. In the next article, we will briefly go through the select-by-category-id
operation of the procedure/article
group, and then discuss another topic of using database
. . [**[Database] Lesson 7 – Some Other Procedures & View Concepts**](https://viblo.asia/p/gDVK2r4eKLj)
Source : Viblo