コード概要
ここでは、Accellaを使ってデータベースを利用したWebアプリケーションを作成する際のコードを簡単に紹介します。
テーブル定義
データベースのテーブル定義とマイグレーションにはPrismaのスキーマを使用します。 定義したテーブルの情報に従って、マイグレーションファイルを生成することができます。
model User { id Int @id @default(autoincrement()) firstName String lastName String age Int?}
O/R マッピング
Accellaでは、ORMライブラリとしてActive Recordパターンで設計されたAccel Recordを使用しています。 同期的なインターフェースにより、awaitを書かなくてもよいこと、アソシエーションの暗黙的な遅延ロードができるという特徴があります。
import { User } from "src/models";
// Create a new userconst user: User = User.create({ firstName: "John", lastName: "Doe",});
// Update the useruser.update({ age: 26,});
// Get all usersfor (const user of User.all()) { console.log(user.firstName);}
// Find a userconst john: User | undefined = User.findBy({ firstName: "John", lastName: "Doe",});
// Delete the userjohn?.delete();
モデルクラスの拡張
モデルに対応したクラスを拡張することで、バリデーション、コールバック、独自のメソッドなどを定義することができます。
import { before } from "accel-record";import { validates } from "accel-record/validations";import { ApplicationRecord } from "./applicationRecord.js";
export class UserModel extends ApplicationRecord { // モデルのバリデーションを定義 static validations = validates(this, [ ["firstName", { presence: true }], ["lastName", { presence: true }], ]);
@before("save") myCallback() { // このメソッドは保存前に呼び出されます }
// フルネームを取得するメソッドを定義 get fullName(): string { return `${this.firstName} ${this.lastName}`; }}
ページの用意
ファイルベースのルーティングになり、基本的にはルーティング用の記述は不要です。 AstroコンポーネントはReactコンポーネントやVueのSFCのように、JavaScriptでの処理とDOM構造を1つのファイルで記述します。これにより、型安全なテンプレートレンダリングが実現できます。
---import { paginate } from "accel-web";import { User } from "src/models";import Layout from "../layouts/Layout.astro";
const page = Number(Astro.locals.params.p) || 1;const pager = paginate(User.order('id', 'desc'), { page, per: 10, window: 2,});const { Nav, PageEntriesInfo } = pager;---
<Layout> <h2>User List</h2> <table> <thead> <tr> <th>ID</th> <th>Email</th> </tr> </thead> <tbody> { pager.records.map((user) => ( <tr> <td>{user.id}</td> <td>{user.email}</td> </tr> )) } </tbody> </table> <!-- Pagination --> <div><Nav /></div> <div><PageEntriesInfo /></div></Layout>
---import { formFor } from "accel-web";import Layout from "src/layouts/Layout.astro";import { User } from "src/models";
// Retrieve the user by id// If the user does not exist, a RecordNotFound error is thrown and a 404 page is displayedconst user = User.find(Astro.params.id);
if (Astro.request.method === "POST") { const { params } = Astro.locals; // Update the email column if (user.update(params.require("user").permit("email"))) { // Redirect to the user list page if the update is successful return Astro.redirect("/users"); } // If the update fails, continue rendering the page and display // the validation error messages from user.errors}const f = formFor(user);const { Form, Label, TextField, Submit } = f;---
<Layout> <h2>Edit User</h2> <Form method="post"> { user.errors.fullMessages.length > 0 && ( <div role="alert"> {user.errors.fullMessages.map((message) => ( <div>{message}</div> ))} </div> ) } <div> <Label for="email" /> <!-- The value of the TextField will reflect the content of user.email --> <TextField attr="email" /> </div> <div> <Submit>Update</Submit> <a href="/users">Cancel</a> </div> </Form></Layout>
テスト
プロジェクト開始後すぐにテストを構築できるように、Vitestがセットアップされています。また、Accel Record用のファクトリーも用意されており、簡単にテスト用のデータを作成することができます。
import { User } from "src/models";import { $User } from "./factories/user";
test("create a user", () => { const user = $User.create({ firstName: "John", lastName: "Doe", });
expect(user.fullName).toBe("John Doe"); expect(User.count()).toBe(1);});