- vừa được xem lúc

[Database] Bài 6 - VIết Code Quản Lý Một Database Đơn Giản (Tiếp Theo)

0 0 27

Người đăng: Semi Art

Theo Viblo Asia

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 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ã encodingutf-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.

[Database] Bài 7 - Khái Niệm View Trong Quản Trị Database

Bình luận

Bài viết tương tự

- vừa được xem lúc

Mô hình quan hệ - thực thể (Entity – Relationship Model)

Mô hình quan hệ thực thể (Entity Relationship model - E-R) được CHEN giới thiệu vào năm 1976 là một mô hình được sử dụng rộng rãi trong các bản thiết kế cơ sở dữ liệu ở mức khái niệm, được xây dựng dựa trên việc nhận thức thế giới thực thông qua tập các đối tượng được gọi là các thực thể và các mối

0 0 132

- vừa được xem lúc

[Embulk #1] Công cụ giúp giảm nỗi đau chuyển đổi dữ liệu

Embulk là gì. Embulk là một công cụ open source có chức năng cơ bản là load các record từ database này và import sang database khác.

0 0 55

- vừa được xem lúc

Window Functions trong MySQL, Nâng cao và cực kì hữu dụng (Phần II).

Chào mọi người, lại là mình đây, ở phần trước mình đã giới thiệu với mọi người về Window Functions Phần I. Nếu chưa rõ nó là gì thì mọi người nên đọc lại trước nha, để nắm được định nghĩa và các key words, tránh mắt chữ O mồm chứ A vì phần này mình chủ yếu sẽ thực hành với các Window Functions.

0 0 107

- vừa được xem lúc

Window Functions trong MySQL, Nâng cao và cực kì hữu dụng (Phần I).

Chào mọi người, mình mới tìm hiểu đc topic Window Functions cá nhân mình cảm thấy khá là hay và mình đánh giá nó là phần nâng cao. Vì ít người biết nên Window Functions thấy rất ít khi sử dụng, thay vì đó là những câu subquery dài dằng dặc như tin nhắn nhắn cho crush, và người khác đọc hiểu được câu

0 0 923

- vừa được xem lúc

Disable và Enable trigger trong Oracle

Origin post: https://www.tranthanhdeveloper.com/2020/12/disable-va-enable-trigger-trong-oracle.html.

0 0 41

- vừa được xem lúc

Lưu trữ dữ liệu với Data Store

. Data Store là một trong những componet của bộ thư viện Android JetPack, nó là một sự lựa chọn hoàn hảo để thay thế cho SharedPreferences để lưu trữ dữ liệu đơn giản dưới dạng key-value. Chúng ta cùng làm một so sánh nhỏ để thấy sự tối ưu của Data Store với SharedPreferences nhé.

0 0 71