[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)

Share the news now

Source : Viblo