-
Notifications
You must be signed in to change notification settings - Fork 2
feature: created docs for setting up db, creating models, and running migrations #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
6513bb2
4c535a4
8b993f1
4967a69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| # Setting up a database | ||
|
|
||
| ## Docker Dev Environment | ||
|
|
||
| ### Creating a database | ||
|
|
||
| Once your dev containers are running and you are inside the container, it is easy to create a development database. | ||
|
|
||
| First, install dependencies. | ||
|
|
||
| ```bash | ||
| pnpm install | ||
| ``` | ||
|
|
||
| Then, create the database. | ||
|
|
||
| ```bash | ||
| pnpm db:create | ||
| ``` | ||
|
|
||
| Finally, run the migrations to build the most current schema. | ||
|
|
||
| ```bash | ||
| pnpm db:migrate | ||
| ``` | ||
|
|
||
| ### Dropping a database | ||
|
|
||
| You can also delete the whole database, which is sometimes useful. Simply run | ||
|
|
||
| ```bash | ||
| pnpm db:drop | ||
| ``` | ||
|
|
||
| **Note:** Any data that was saved will be lost and unrecoverable! | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| # Database Migrations | ||
|
|
||
| Our Chatbot db uses [Sequelize CLI](https://sequelize.org/docs/v6/other-topics/migrations/) to manage database migrations. | ||
|
|
||
| ## Creating a Migration | ||
|
|
||
| To create a database migration skeleton, use the following command found in `package.json`. | ||
|
|
||
| ``` bash | ||
| pnpm db:generate-migration --name <some_informative_name_about_your_model> | ||
| ``` | ||
|
|
||
| In `src/migrations`, you should now find a new migration file with an auto-generated timestamp. | ||
|
|
||
| Change the new migration's file extension to `.cjs`. | ||
|
|
||
| ``` bash | ||
| 202405121234-test.js ==> 202405121234-test.cjs | ||
| ``` | ||
|
|
||
| `*.js` files are set up to have a linting error if we use CommonJS style imports and exports, so `*.cjs` is used be explicit about the fact that we are using a different workflow from the rest of the project. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we change |
||
|
|
||
| ## Adding Schema Changes | ||
|
|
||
| Below is the body of a sample migration to use as an example. Note that | ||
| the underlying Postgresql table should have **snake_cased** column names, | ||
| but that the table attributes should be **camelCase**. There is currently no way to appropriately set the table to handle auto-updating the `created_at` timestamp, but our models will take of it automatically. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure about
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I need to correct that, thank you! |
||
|
|
||
| See the [Sequelize Docs](https://sequelize.org/) for more examples and info on adding indexes, etc. | ||
|
|
||
| ``` javascript | ||
| 'use strict'; | ||
|
|
||
| module.exports = { | ||
| up: async (queryInterface, Sequelize) => { | ||
| await queryInterface.createTable('tests', { | ||
| id: { | ||
| type: Sequelize.UUID, | ||
| defaultValue: Sequelize.UUIDV4, | ||
| primaryKey: true, | ||
| allowNull: false | ||
| }, | ||
| name: { | ||
| type: Sequelize.STRING(100), | ||
| allowNull: false | ||
| }, | ||
| description: { | ||
| type: Sequelize.TEXT, | ||
| allowNull: true | ||
| }, | ||
| is_active: { | ||
| type: Sequelize.BOOLEAN, | ||
| defaultValue: true | ||
| }, | ||
| difficulty_level: { | ||
| type: Sequelize.ENUM('easy', 'medium', 'hard'), | ||
| defaultValue: 'medium' | ||
| }, | ||
| max_score: { | ||
| type: Sequelize.INTEGER | ||
| }, | ||
| created_at: { | ||
| type: Sequelize.DATE, | ||
| allowNull: false, | ||
| defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') | ||
| }, | ||
| updated_at: { | ||
| type: Sequelize.DATE, | ||
| allowNull: false, | ||
| defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') | ||
| }, | ||
| deleted_at: { | ||
| type: Sequelize.DATE, | ||
| allowNull: true | ||
| } | ||
| }); | ||
|
|
||
| }, | ||
|
|
||
| down: async (queryInterface) => { | ||
| await queryInterface.dropTable('tests'); | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| ## Undoing a Migration | ||
|
|
||
| **These commands should be used very carefully!** | ||
| **Rolling back a migration may cause data loss!** | ||
|
|
||
| You can undo the most recent migration with | ||
|
|
||
| ```bash | ||
| pnpm db:undo-migration | ||
| ``` | ||
|
|
||
| or undo the last n migrations with | ||
|
|
||
| ```bash | ||
| pnpm db:rollback-migrations --to <first_migration_of_the_ones_to_rollback> | ||
| ``` | ||
|
|
||
| **Note:** The migration specified in the `--to` argument is included in the rollback! | ||
|
|
||
| Finally, undo every migration with | ||
|
|
||
| ```bash | ||
| pnpm db:rollback-migrations | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| # Creating an ORM Model | ||
|
|
||
| Models are the way that we interact with our chatbot_server's underlying database. | ||
|
|
||
| First, create a new model in `src/models`. | ||
|
|
||
| ```bash | ||
| touch src/models myModel.js(the name of your model) # Models should be camelCase | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest separating shell commands from inline comments: touch src/models/myModel.js |
||
| ``` | ||
|
|
||
| Second, create the model in the following format | ||
|
|
||
| ```javascript | ||
| 'use strict'; | ||
| const { Model } = require('sequelize'); | ||
|
|
||
| module.exports = (sequelize, DataTypes) => { | ||
| class Test extends Model { | ||
| static associate(models) { | ||
| // Define associations here (example) | ||
| // this.hasMany(models.TestResult, { foreignKey: 'test_id' }); | ||
| } | ||
| } | ||
|
|
||
| Test.init({ | ||
| id: { | ||
| type: DataTypes.UUID, | ||
| defaultValue: DataTypes.UUIDV4, | ||
| primaryKey: true, | ||
| allowNull: false | ||
| }, | ||
| name: { | ||
| type: DataTypes.STRING(100), | ||
| allowNull: false, | ||
| validate: { | ||
| notEmpty: true, | ||
| len: [3, 100] | ||
| } | ||
| }, | ||
| description: { | ||
| type: DataTypes.TEXT, | ||
| allowNull: true | ||
| }, | ||
| is_active: { | ||
| type: DataTypes.BOOLEAN, | ||
| defaultValue: true | ||
| }, | ||
| difficulty_level: { | ||
| type: DataTypes.ENUM('easy', 'medium', 'hard'), | ||
| defaultValue: 'medium' | ||
| }, | ||
| max_score: { | ||
| type: DataTypes.INTEGER, | ||
| validate: { | ||
| min: 0, | ||
| max: 1000 | ||
| } | ||
| } | ||
| }, { | ||
| sequelize, | ||
| modelName: 'Test', | ||
| tableName: 'tests', | ||
| timestamps: true, | ||
| underscored: true, | ||
| paranoid: true, // Enables soft deletes | ||
| }); | ||
|
|
||
| return Test; | ||
| }; | ||
| ``` | ||
|
|
||
|
|
||
|
|
||
| ## Using the Model | ||
|
|
||
| You can go ahead and import the model in your service, and/or tests file as such and perform operations on the model given the demands of the tasks you're working on as such. | ||
|
|
||
| ```javascript | ||
| // Import the model | ||
| const { Test } = require('./models'); | ||
|
|
||
| // 1. Create a test | ||
| const newTest = await Test.create({ | ||
| name: 'Math Assessment', | ||
| description: 'Basic algebra test', | ||
| difficulty_level: 'medium', | ||
| max_score: 100 | ||
| }); | ||
|
|
||
| // 2. Fetch tests | ||
| const tests = await Test.findAll({ | ||
| where: { is_active: true }, | ||
| limit: 10 | ||
| }); | ||
|
|
||
| // 3. Update a test | ||
| const [updatedRows] = await Test.update( | ||
| { max_score: 150 }, | ||
| { where: { id: testId } } | ||
| ); | ||
|
|
||
| // 4. Soft delete | ||
| await Test.destroy({ where: { id: testId } }); | ||
|
|
||
| ``` | ||
|
|
||
| Once you restart the app, the Sequelize instance will be able to use the model. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am thinking about changing
Once your dev containerstoOnce your dev containersince the latter part of the sentence is talking about a single container?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think the statement is correct because we would have containers running for our chatbot_db and chatbot_server, so I would need to correct the statement to
Once your dev containers are running and you are inside the chatbot_server containerto show emphasis on where we will be creating tables in our db.Thank you for pointing that out, Saumil.