[Database] Bài 6 – Viết Code Quản Lý Database (Tiếp Theo)

Tới bây giờ thì chúng ta đã khá quen thuộc với cấu trúc thư mục và việc phân chia các tác vụ nhỏ sub-procedure rồi; Do đó nên trong bài viết này chúng ta sẽ đi nhanh qua các procedure cơ bản còn lại là select, update, và delete. Hãy cùng bắt đầu với thủ

Tới bây giờ thì chúng ta đã khá quen thuộc với cấu trúc thư mục và việc phân chia các tác vụ nhỏ sub-procedure rồi; Do đó nên trong bài viết này chúng ta sẽ đi nhanh qua các procedure cơ bản còn lại là select, update, và delete. Hãy cùng bắt đầu với thủ tục select.

select

Chính xác hơn thì là select-by-id – đây là thủ tục select cơ bản nhất và có thể được sử dụng để làm chất liệu cho các procedure khác phức tạp hơn. Về cơ bản thì ở đây chúng ta chỉ cần thực hiện một vài bước như sau – tìm đường dẫn tới thư mục bản ghi tương ứng với id được cung cấp, sau đó đọc dữ liệu từ các tệp header.jsoncontent.md để nạp vào object kết quả thuộc class Category.

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 =newCategory())=>{try{/* --- find record's folder path */var found ={ recordFolderPath:""};awaitfindRecordFolderPathById(in_recordId, found);/* --- read record's header and content */awaitreadRecordHeader(found.recordFolderPath, out_selected);awaitreadRecordContent(found.recordFolderPath, out_selected);}catch(error){throw error;}};// module.exports

Việc tìm kiếm đường dẫn tới thư mục bản ghi tương ứng thì chỉ đơn giản là chúng ta đọc tên của tất cả các thư mục và lọc ra kết quả phù hợp thôi. Để thu thập đường dẫn thư mục của tất cả các bản ghi thì chúng ta có thể sử dụng lại một sub-procedure đã viết trước đó. Còn thao tác lọc ra kết quả phù hợp thì chúng ta sẽ sử dụng vòng lặp for để phù hợp với tinh thần PP thay vì sử dụng phương thức lặp .forEach của mảng.

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 =[];awaitreadAllRecordFolderNames(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

Việc viết code chạy thử các sub-procedure thì mình sẽ lược giản bớt nhé. Ở đây chúng ta sẽ chỉ viết code chạy thử các procedure chính thôi. Như vậy chúng ta sẽ có thể duy trì nội dung bài viết gọn gàng hơn và không quá dài.

Sau khi đã tìm được đường dẫn path tới thư mục của bản ghi tương ứng, chúng ta tiến hành đọc các tệp dữ liệu và nạp vào object kết quả thôi. Tuy nhiên đối với các thao tác đọc nội dung từ các tệp, chúng ta cần đảm bảo rằng kết quả thu được sẽ ở dạng văn bản với mã encodingutf-8.

const Category =require("../../../type/Category");const fsPromises =require("fs/promises");const path =require("path");

module.exports =async(
   in_recordFolderPath ="",
   out_record =newCategory())=>{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
const Category =require("../../../type/Category");const fsPromises =require("fs/promises");const path =require("path");

module.exports =async(
   in_recordFolderPath ="",
   out_record =newCategory())=>{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

Và bây giờ thì chúng ta đã có thể viết code chạy thử thủ tục select-by-id.

const Category =require("./database/type/Category");const databaseManager =require("./database/manager");voidasyncfunction(){var selected =newCategory();await databaseManager.execute("select-category-by-id","01", selected);

   console.log(selected);}();// void
npm test

Category(4) [Map] {
   '@id' => '01',
   'name' => 'html',
   'keywords' => [ 'hướng dẫn cơ bản', 'lập trình web', 'html' ],
   'markdown' => 'Nội dung của trang đơn mô tả danh mục HTML...'
}

update

Trường hợp sử dụng của update là khi chúng ta mở một bài viết đã đăng tải trước đó để chỉnh sửa nội dung và sau đó lưu lại. Lúc này thông tin được gửi về server sẽ là đầy đủ các cặp key/value của một object Category. Code xử lý của route tương ứng sẽ tạo ra một object bản ghi thuộc class Category và gọi thủ tục update.

const writeRecordToDataFolder =require("./sub-procedure/write-record-to-data-folder--async-throw");const Category =require("../../type/Category");

module.exports =async(
   in_record =newCategory(),
   out_updated =newCategory())=>{try{awaitwriteRecordToDataFolder(in_record);
      Category.clone(in_record, out_updated);}catch(error){throw error;}};// module.exports

So với insert thì ở đây chúng ta không cần thực hiện thao tác khởi tạo giá trị id mới. Tuy nhiên ở đây chúng ta sẽ cần sửa lại sub-procedure ghi dữ liệu vào thư mục bản ghi mà chúng ta đã định nghĩa trước đó. Trường hợp lúc này là chúng ta đã có thư mục tương ứng với bản ghi cần cập nhật và thao tác fsPromises.mkdir(recordPath) trong code mà chúng ta đã viết dưới đây sẽ báo lỗi là thư mục đã tồn tại.

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 =newCategory())=>{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 */awaitwriteRecordHeaderToFile(in_record, recordFolderPath);awaitwriteRecordContentToFile(in_record, recordFolderPath);}catch(error){throw error;}};// module.exports

Các thao tác ghi dữ liệu vào các tệp thì chắc chắn sẽ không có vấn đề gì, bởi vì theo tài liệu của NodeJS cung cấp thì thao tác fsPromises.writeFile(filePath) sẽ tự động thay thế tệp đã tồn tại bằng tệp mới. Do đó nên chúng ta chỉ cần thêm điều kiện kiểm tra xem thư mục bản ghi đã tồn tại chưa trước khi quyết định khởi tạo đường dẫn cho thư mục mới và chạy lệnh fsPromises.mkdir(recordPath).

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 =newCategory())=>{try{var found ={ recordFolderPath:""};awaitfindRecordFolderPathById(in_record.get("@id"), found);var recordExists =(found.recordFolderPath !="");if(recordExists){awaitwriteRecordHeaderToFile(in_record, found.recordFolderPath);awaitwriteRecordContentToFile(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 */awaitwriteRecordHeaderToFile(in_record, newRecordFolderPath);awaitwriteRecordContentToFile(in_record, newRecordFolderPath);}// else}catch(error){throw error;}};// module.exports

Ở đây mình chọn viết lặp lại các lời gọi thủ tục writeRecord... cho mỗi trường hợp của kết quả tìm kiếm thư mục bản ghi tương ứng với id. Bạn có thể xử lý theo cách khác là tạo ra một biến lưu đường dẫn thư mục ở đầu tiên và thay đổi giá trị của biến đó bằng khối điều kiện if ... else ...; Rồi sau đó sử dụng biến chứa đường dẫn thư mục đó để gọi các thủ tục ghi dữ liệu vào các tệp một lần duy nhất ở cuối cùng.

const Category =require("./database/type/Category");const databaseManager =require("./database/manager");voidasyncfunction(){var selected =newCategory();var updated =newCategory();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
npm test

Category(4) [Map] {
  '@id' => '01',
  'name' => 'html5',
  'keywords' => [ 'hướng dẫn cơ bản', 'lập trình web', 'html' ],
  'markdown' => 'Nội dung của trang đơn mô tả danh mục HTML...'
}
Category(4) [Map] {
  '@id' => '02',
  'name' => 'css3',
  'keywords' => [ 'hướng dẫn cơ bản', 'lập trình web', 'css' ],
  'markdown' => 'Nội dung của trang đơn mô tả danh mục CSS...'
}

delete

Trường hợp sử dụng của delete là khi chúng ta chọn nút nhấn xóa một danh mục trên giao diện web quản lý các danh mục bài viết. Dữ liệu được gửi về server thường sẽ chỉ cần duy nhất thành phần định danh của danh mục đó là id.

Thao tác mà chúng ta cần xử lý đầu tiên là – kiểm tra xem có bài viết article nào đang thuộc danh mục này không; Nếu có thì cần thông báo ngoại lệ, còn nếu không thì chúng ta có thể tiến hành xóa bản ghi category tương ứng và trả về kết quả là thông tin của bản ghi đã được xóa khỏi database.

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 =newCategory())=>{try{var selectedArticles =[];awaitselectArticlesByCategoryId(in_recordId, selectedArticles);var theCategoryContainsSomeArticles =(selectedArticles.length !=0);if(theCategoryContainsSomeArticles)thrownewError("Đang có bài viết thuộc danh mục này");elseawaitremoveRecordFromDatabase(in_recordId, out_deleted);}catch(error){throw error;}};// module.exports

Để kiểm tra xem có bài viết article nào đang thuộc danh mục chỉ định hay không thì chúng ta sẽ tìm trong số tất cả các bản ghi article để lọc ra các bản ghi có category-id tương ứng. Đây cũng là procedure đầu tiên mà chúng ta tạo ra cho nhóm procedure/article. Tuy nhiên chúng ta hãy cứ tạm giả định là không có bài viết nào thuộc danh mục cần xóa và để dành procedure này cho phần thảo luận sau cùng nhé.

module.exports =async(
   in_categoryId ="Infinity",
   out_matchedArticles =[])=>{/* do nothing */;};

Và trong trường hợp không có bài viết nào đang thuộc danh mục chỉ định, thì việc xóa các tệp dữ liệu và thư mục của bản ghi category này sẽ được ủy thác cho một sub-procedure có tên removeRecord... như trên.

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 =newCategory())=>{try{awaitselectRecordById(in_recordId, out_deleted);var found ={ recordFolderPath:""};awaitfindRecordFolderPathById(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

Bây giờ chúng ta cứ viết code chạy thử delete-by-id cho trường hợp danh mục không chứa bài viết nào đã. Trường hợp còn lại cứ để tính sau đi. 😄

const Category =require("./database/type/Category");const databaseManager =require("./database/manager");voidasyncfunction(){var deleted =newCategory();await databaseManager.execute("delete-category-by-id","02", deleted)
   console.log(deleted);}();// void
npm test

Category(4) [Map] {
   '@id' => '02',
   'name' => 'css3',
   'keywords' => [ 'hướng dẫn cơ bản', 'lập trình web', 'css' ],
   'markdown' => 'Nội dung của trang đơn mô tả danh mục CSS...'
}

Kết thúc bài viết 😄

Mình xin lỗi nhưng có lẽ là chúng ta sẽ không mang một thủ tục của nhóm procedure/Article vào để thảo luận trong bài viết này, nhằm mục đích duy trì trọng tâm của bài viết xoay quanh các procedure cơ bản đối với nhóm category. Ở thời điểm hiện tại thì mình tin chắc chắn rằng bạn đã có thể tự hoàn thành code truy vấn tất cả các bản ghi article và lọc ra những bản ghi có category-id tương ứng.

Chúng ta đã khá quen với các thao tác làm việc với các thư mục và đọc dữ liệu từ các tệp rồi, vì vậy nên những procedure phát sinh do nhu cầu thiết kế blog cá nhân của bạn hiển nhiên cũng sẽ không thể làm khó bạn được nữa.

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ