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.json
và content.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 function( 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
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 function( 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
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ã encoding
là utf-8
.
const Category = require("../../../type/Category")
const fsPromises = require("fs/promises")
const path = require("path") module.exports = async function( 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
const Category = require("../../../type/Category")
const fsPromises = require("fs/promises")
const path = require("path") module.exports = async function( 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
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") 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' => [ '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 function( 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
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 = 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
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 = 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) } } 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") 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' => [ '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 = new Category()
) => { try { var selectedArticles = [] await selectArticlesByCategoryId(in_recordId, selectedArticles) var theCategoryContainsSomeArticles = (selectedArticles.length != 0) if (theCategoryContainsSomeArticles) throw new Error("Đang có bài viết thuộc danh mục này") else await removeRecordFromDatabase(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 = 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
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") 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' => [ '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. Trong bài viết tiếp theo, chúng ta sẽ lướt qua nhanh thao tác select-by-category-id
của nhóm procedure/article
, và sau đó sẽ cùng thảo luận về một chủ đề khác trong việc sử dụng database
.