チュートリアル
TreeFrog アプリケーションの作成をひとめぐりしてみましょう。
リスト表示、テキストの追加/編集/削除ができる単純なブログシステムをつくってみます。
アプリケーションのスケルトンを生成
blogapp という名でスケルトン(ディレクトリツリーと各種設定ファイル)を作ってみます。コマンドライン から次を実行します。Windows の場合は、TreeFrog Command Prompt 上で実行してください。
$ tspawn new blogapp
created blogapp
created blogapp/controllers
created blogapp/models
created blogapp/models/sqlobjects
created blogapp/views
created blogapp/views/layouts
created blogapp/views/mailer
created blogapp/views/partial
:
テーブルを作成
データベースにテーブルを作ります。タイトルと内容(ボディ)のフィールドを作りましょう。
ここでは、MySQL と SQLite の例を示します。
MySQL の例:
文字セットは UTF-8 に設定します。データベースの設定ファイルでその指定するか(正しく設定されたか確認しましょう。FAQ 参照)、下記のようにデータベースを生成する際に指定することもできます。また、mysql コマンドラインツールにパスを通しておいてください。
$ mysql -u root -p
Enter password:
mysql> CREATE DATABASE blogdb DEFAULT CHARACTER SET utf8mb4;
Query OK, 1 row affected (0.01 sec)
mysql> USE blogdb;
Database changed
mysql> CREATE TABLE blog (id INTEGER AUTO_INCREMENT PRIMARY KEY, title VARCHAR(20), body VARCHAR(200), created_at DATETIME, updated_at DATETIME, lock_revision INTEGER) DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.02 sec)
mysql> DESC blog;
+---------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| title | varchar(20) | YES | | NULL | |
| body | varchar(200) | YES | | NULL | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
| lock_revision | int(11) | YES | | NULL | |
+---------------+--------------+------+-----+---------+----------------+
6 rows in set (0.01 sec)
mysql> quit
Bye
SQLite の例:
データベースファイルは db ディレクトリに置くことにします。
$ cd blogapp
$ sqlite3 db/blogdb
SQLite version 3.6.12
sqlite> CREATE TABLE blog (id INTEGER PRIMARY KEY AUTOINCREMENT, title VARCHAR(20), body VARCHAR(200), created_at TIMESTAMP, updated_at TIMESTAMP, lock_revision INTEGER);
sqlite> .quit
これで id、title, bady, created_at, updated_at, lock_revision のフィールドを持つ blog テーブルができました。
created_at と updated_at のフィールドがあると、TreeFrog はそれぞれ作成日時、更新日時を自動で挿入・更新してくれます。lock_revision フィールドは、楽観的ロックを実現するためのもので、integer 型で作っておきます。
楽観的ロック (Optimistic Lock)
楽観的ロックとは、更新の時に行ロックをかけず、他から更新されていないことを検証しつつデータを保存することです。実際のロックはかけないので、処理速度の向上がちょっとだけ期待できます。詳細はO/R マッピングの章をご覧ください。
データベースの情報を設定
データベースの情報を config/database.ini ファイルに設定します。
エディタでファイルを開き、[dev] の各項目に環境に応じた適切な値を入力して、保存します。
MySQL の例:
[dev]
DriverType=QMYSQL
DatabaseName=blogdb
HostName=
Port=
UserName=root
Password=pass
ConnectOptions=
SQLite の例:
[dev]
DriverType=QSQLITE
DatabaseName=db/blogdb
HostName=
Port=
UserName=
Password=
ConnectOptions=
正しく設定されたか、DB にアクセスしてテーブルを表示してみましょう。
$ cd blogapp
$ tspawn --show-tables
DriverType: QSQLITE
DatabaseName: db\blogdb
HostName:
Database opened successfully
-----
Available tables:
blog
このように表示されれば成功です。
もし 使用する SQL ドライバが Qt SDK に組み込まれていないと、ここでエラーが発生します。
QSqlDatabase: QMYSQL driver not loaded
Qt の SQL ドライバがインストールされていない可能性があります。RDBM の Qt ドライバをインストールしてください。
組み込まれた SQL ドライバは次のコマンドで確認することができます。
$ tspawn --show-drivers
Available database drivers for Qt:
QSQLITE
QMYSQL3
QMYSQL
QODBC3
QODBC
SQLite の SQL ドライバはあらかじめ組み込まれているので、ちょっと試すだけなら SQLite を使うのがいいかもしれませんね。
テンプレートシステムの指定
TreeFrog Framework では、テンプレートシステムとして ERB と Otama のどちらかを指定します。 development.ini ファイルにある TemplateSystem パラメータに設定します。
TemplateSystem=ERB
または
TemplateSystem=Otama
デフォルトでは ERB が指定されます。
作ったテーブルからコードを自動生成
コマンドラインから、ジェネレータコマンド(tspawn)を実行し、ベースとなるコードを生成します。下記の例ではコントローラ、モデル、ビューを生成しています。引数には、テーブル名を指定します。
$ tspawn scaffold blog
DriverType: QSQLITE
DatabaseName: db/blogdb
HostName:
Database open successfully
created controllers/blogcontroller.h
created controllers/blogcontroller.cpp
updated controllers/controllers.pro
created models/sqlobjects/blogobject.h
created models/blog.h
created models/blog.cpp
updated models/models.pro
created views/blog
:
tspawn の オプションによって、コントローラだけあるいはモデルだけを生成することができます。
Vue.js サポート
バージョン 2 以降、vue.js を使用したビューを生成することを選択することが可能です。
:
Create sources for vue.js? [y/n] y ← yを入力
参考:tspawn コマンドヘルプ
$ tspawn --help
usage: tspawn <subcommand> [args]
Type 'tspawn --show-drivers' to show all the available database drivers for Qt.
Type 'tspawn --show-driver-path' to show the path of database drivers for Qt.
Type 'tspawn --show-tables' to show all tables to user in the setting of 'dev'.
Type 'tspawn --show-collections' to show all collections in the MongoDB.
Available subcommands:
new (n) <application-name>
scaffold (s) <table-name> [model-name]
controller (c) <controller-name> action [action ...]
model (m) <table-name> [model-name]
helper (h) <name>
usermodel (u) <table-name> [username password [model-name]]
sqlobject (o) <table-name> [model-name]
mongoscaffold (ms) <model-name>
mongomodel (mm) <model-name>
websocket (w) <endpoint-name>
api (a) <api-name>
validator (v) <name>
mailer (l) <mailer-name> action [action ...]
delete (d) <table-name, helper-name or validator-name>
ソースコードをビルド
make する前に、一度だけ次のコマンドを実行し、Makefile を生成します。
$ qmake -r "CONFIG+=debug"
WARNING メッセージが表示されますが、問題はありません。その後、make コマンドを実行すると、コントローラ、モデル、ビュー、ヘルパの全てをコンパイルします。
$ make (MSVCの場合は nmake)
ビルドが成功すると、4つの共有ライブラリ(controller, model, view, helper)が lib ディレクトリに作られます。
デフォルトでは、デバッグモードのライブラリが生成されますが、リリースモードのライブラリを作成するには次のコマンドで Makefile を再生成すればよいでしょう。
リリースモードの Makefile 作成:
$ qmake -r "CONFIG+=release"
アプリケーションサーバを起動
アプリケーションのルートディレクトリに移り、アプリケーションサーバ(AP サーバ)を起動します。
サーバは、コマンドが実行されたディレクトリをアプリケーションルートディレクトリと見なして処理を始めます。サーバを止めるときは、Ctrl+c を押します。
$ treefrog -e dev
Windows では、treefrogd.exe を使って起動します。
> treefrogd.exe -e dev
Windows では、Web アプリケーションをデバッグモードでビルドした場合は treefrogd.exe を、リリースモードでビルドした場合は treefrog.exe を使って起動してください。
リリースモードとデバッグモードのオブジェクトが混在すると、正常に動作しません。
バックグランドで起動する場合は、-d オプションを指定します。
$ treefrog -d -e dev
ここまで現れた -e オプションについて補足します。
このオプションの後に database.ini で設定したセクション名を指定することで、データベースの設定を切り替えることができます。省略時は、product を指定したとみなされます。プロジェクトの作成時は次の3つが定義されています。
セクション | 説明 |
---|---|
dev | 開発用、ジェネレータ用 |
test | テスト用 |
product | 正式版用、製品版用 |
※ -e は environment の頭文字からきています。
停止コマンド:
$ treefrog -k stop
強制終了コマンド:
$ treefrog -k abort
リスタートコマンド:
$ treefrog -k restart
★ もしファイヤーウォールが設定されている場合は、ポート(デフォルト:8800)を開けてください。
次のコマンドで URL ルーティングを確認できます。
$ treefrog --show-routes
Available controllers:
match /blog/index -> blogcontroller.index()
match /blog/show/:param -> blogcontroller.show(id)
match /blog/create -> blogcontroller.create()
match /blog/save/:param -> blogcontroller.save(id)
match /blog/remove/:param -> blogcontroller.remove(id)
参考:treefrogコマンドのヘルプ
$ treefrog -h
Usage: treefrog [-d] [-p port] [-e environment] [-r] [app-directory]
Usage: treefrog -k [stop|abort|restart|status] [app-directory]
Usage: treefrog -m [app-directory]
Options:
-d : run as a daemon process
-p port : run server on specified port
-e environment : specify an environment of the database settings
-k : send signal to a manager process
-m : show the process ID of a running main program
-r : reload app automatically when updated (for development)
Type 'treefrog --show-routes [app-directory]' to show routing information.
Type 'treefrog --settings [app-directory]' to show application settings.
Type 'treefrog -l' to show your running applications.
Type 'treefrog -h' to show this information.
Type 'treefrog -v' to show the program version.
ブラウザでアクセス
ブラウザで http://localhost:8800/Blog にアクセスしてみましょう。
次のような一覧画面が表示されるはずです。
最初は1件も登録がありません。
2件ほど登録してみたところ。すでに新規登録、参照、編集、削除を行うができます。
日本語の表示も問題なし。とっても簡単!
他のフレームワークと同様に TreeFrog においても、リクエストされた URL から該当するコントローラのメソッド(アクション)を呼び出す仕組み(ルーティングシステム)が備わっています。
開発したソースコードはビルドしなおせば、他のプラットフォームでも動作します。
このサンプル Web アプリケーションを公開してます。ここにアクセスして、遊んでみてください。デスクトップアプリケーション並の速さです。
コントローラの中身
生成されたコントローラの中身を見てみましょう。
public slots の部分に、ディスパッチさせたいアクション(メソッド)を宣言するのがポイントです。そこには CRUD に相当するアクションが定義されていますね。
ちなみに、slots キーワードは Qt による機能拡張のものです。詳細は Qt ドキュメントをご覧ください。
class T_CONTROLLER_EXPORT BlogController : public ApplicationController {
Q_OBJECT
public slots:
void index(); // 一覧表示
void show(const QString &id); // 1件表示
void create(); // 新規登録
void save(const QString &id); // 保存(更新)
void remove(const QString &id); // 1件削除
};
次はソースファイルです。コントローラはリクエストに応じてビューを呼び出す役割を担っています。サービスを呼び出し、その結果に応じて render 関数でテンプレートを呼び出したり、redirect()関数でリダイレクトさせたりします。 主要な処理はサービスクラスで行い、コントローラのロジックはシンプルにすることが重要です。
static BlogService service;
void BlogController::index()
{
service.index(); // サービス呼び出し
render(); // ビュー(index.erb)を描画
}
void BlogController::show(const QString &id)
{
service.show(id.toInt()); // サービス呼び出し
render(); // ビュー(show.erb)を描画
}
void BlogController::create()
{
int id;
switch (request().method()) { // httpRequestメソッドのタイプをチェック
case Tf::Get: // GETメソッドの場合
render();
break;
case Tf::Post: // POSTメソッドの場合
id = service.create(request()); // サービス呼び出し
if (id > 0) {
redirect(urla("show", id)); // リダイレクト
} else {
render(); // ビュー(create.erb)を描画
}
break;
default:
renderErrorResponse(Tf::NotFound);
break;
}
}
void BlogController::save(const QString &id)
{
int res;
switch (request().method()) {
case Tf::Get:
service.edit(session(), id.toInt()); // サービス呼び出し
render();
break;
case Tf::Post:
res = service.save(request(), session(), id.toInt()); // サービス呼び出し
if (res > 0) {
// 保存成功
redirect(urla("show", id)); // /blog/show へリダイレクト
} else if (res < 0) {
// 保存失敗
render(); // ビュー(save.erb)を描画
} else {
// リトライ
redirect(urla("save", id)); // /blog/save へリダイレクト
}
break;
default:
renderErrorResponse(Tf::NotFound);
break;
}
}
void BlogController::remove(const QString &id)
{
switch (request().method()) {
case Tf::Post:
service.remove(id.toInt()); // サービス呼び出し
redirect(urla("index")); // /blog/index へリダイレクト
break;
default:
renderErrorResponse(Tf::NotFound);
break;
}
}
// Don't remove below this line
T_DEFINE_CONTROLLER(BlogController)
サービスクラスではリクエストで処理すべき本来のロジック(ビジネスロジック)を記述します。 データベースから取得したモデルオブジェクトを加工しビューへ渡したり、あるいはリクエストから取得したデータをモデルオブジェクト経由でデータベースへ保存したりします。フォームデータのバリデーションを行うこともできます。
void BlogService::index()
{
auto blogList = Blog::getAll(); // Blogオブジェクトの全リストを取得
texport(blogList); // ビューへ渡す
}
void BlogService::show(int id)
{
auto blog = Blog::get(id); // プライマリキーでBlogモデルを取得
texport(blog); // ビューへ渡す
}
int BlogService::create(THttpRequest &request)
{
auto items = request.formItems("blog"); // フォームデータを取得
auto model = Blog::create(items); // Blogオブジェクトを生成
if (model.isNull()) {
QString error = "Failed to create."; // 失敗時のエラーメッセージ
texport(error);
return -1;
}
QString notice = "Created successfully.";
tflash(notice); // flashメッセージを設定
return model.id();
}
void BlogService::edit(TSession& session, int id)
{
auto model = Blog::get(id); // オブジェクト取得
if (!model.isNull()) {
session.insert("blog_lockRevision", model.lockRevision()); // ロックリビジョン番号をセッションに保存
auto blog = model.toVariantMap();
texport(blog); // ビューへ渡す
}
}
int BlogService::save(THttpRequest &request, TSession &session, int id)
{
int rev = session.value("blog_lockRevision").toInt(); // ロックリビジョン番号をセッションから取得
auto model = Blog::get(id, rev); // オブジェクト取得
if (model.isNull()) {
QString error = "Original data not found. It may have been updated/removed by another transaction.";
tflash(error);
return 0;
}
auto blog = request.formItems("blog"); // フォームデータを取得
model.setProperties(blog); // フォームデータを設定
if (!model.save()) { // DBに保存
texport(blog);
QString error = "Failed to update.";
texport(error);
return -1;
}
QString notice = "Updated successfully.";
tflash(notice);
return 1;
}
bool BlogService::remove(int id)
{
auto blog = Blog::get(id); // Blog オブジェクトを取得
return blog.remove(); // DBから削除
}
※ ロックリビジョンは楽観的ロックを実現するために使用されます。詳細は「モデル」の章で後述します。
ご覧のとおり、ビュー(テンプレート)に対してデータを渡すには texport メソッドを使います。この texport メソッドの引数は QVariant のオブジェクトです。QVariant はあらゆる型になりえるので、int, QString, QList, QHash はもちろん任意のオブジェクトが渡せます。QVariant の詳細は Qt ドキュメントを参照ください。
ビューの仕組み
TreeFrog では、今のところ2つのテンプレートシステムを採用しています。ERB と 独自システム(Otama と呼んでいます)です。Rails などで知られているとおり、ERB は HTML にコードを埋め込むものです。
ジェネレータで自動生成されるデフォルトのビューは ERB のファイルです。index.erb の中身を見てみましょう。
ご覧のように <% .. %> で囲まれた部分に C++コードを書きます。index アクションから render メソッドが呼び出されると、この index.erb の内容がレスポンスとして返されます。
<!DOCTYPE HTML>
<%#include "blog.h" %>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title><%= controller()->name() + ": " + controller()->activeAction() %></title>
</head>
<body>
<h1>Listing Blog</h1>
<%== linkTo("New entry", urla("entry")) %><br />
<br />
<table border="1" cellpadding="5" style="border: 1px #d0d0d0 solid; border-collapse: collapse;">
<tr>
<th>ID</th>
<th>Title</th>
<th>Body</th>
</tr>
<% tfetch(QList<Blog>, blogList); %>
<% for (const auto &i : blogList) { %>
<tr>
<td><%= i.id() %></td>
<td><%= i.title() %></td>
<td><%= i.body() %></td>
<td>
<%== linkTo("Show", urla("show", i.id())) %>
<%== linkTo("Edit", urla("save", i.id())) %>
<%== linkTo("Remove", urla("remove", i.id()), Tf::Post, "confirm('Are you sure?')") %>
</td>
</tr>
<% } %>
</table>
もう1つのテンプレートシステムも見てみましょう
Otama はテンプレートとプレゼンテーションロジックを完全に分離したテンプレートシステムです。HTML テンプレートには完全な HTML を記述し、動的に書き換えたい部分の要素(開始タグ)に「マーク」をつけます。プレゼンテーションロジックファイルには、その「マーク」に関連づけてロジック(C++コード)を記述します。
次の例は、テンプレートシステムに Otama を指定した時にジェネレータによって生成されるファイルです。ファイルを見ると分かりますが、HTML(バージョン 5)に準拠しているので、今時のブラウザで開けばデザインは全く崩れません。
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title data-tf="@head_title"></title>
</head>
<body>
<h1>Listing Blog</h1>
<a href="#" data-tf="@link_to_entry">New entry</a><br />
<br />
<table border="1" cellpadding="5" style="border: 1px #d0d0d0 solid; border-collapse: collapse;">
<tr>
<th>ID</th>
<th>Title</th>
<th>Body</th>
<th></th>
</tr>
<tr data-tf="@for"> ← @for という「マーク」。以下同様。
<td data-tf="@id"></td>
<td data-tf="@title"></td>
<td data-tf="@body"></td>
<td>
<a data-tf="@linkToShow">Show</a>
<a data-tf="@linkToEdit">Edit</a>
<a data-tf="@linkToRemove">Remove</a>
</td>
</tr>
</table>
</html>
「マーク」をつけるために data-tf というカスタム属性を使っています。HTML5 でいうところの Custom Data Attribute のことです。その値の”@”で始まっている文字列が「マーク」です。
次に、プレゼンテーションロジックに該当する index.otm を見てみましょう。
上記のテンプレートで宣言されたマークに、ロジックが関連づけられています。マークから空行までが1セットです。ロジックの部分はほぼ C++ コードです。
関連づけには演算子(~= とか :== など)も使われています。この演算子によって、振る舞いが変わります(詳細は各章で)。
#include "blog.h" ← これは C++ コードそのまま。blog.h をインクルード
@head_title ~= controller()->controllerName() + ": " + controller()->actionName()
@for :
tfetch(QList<Blog>, blogList); /* コントローラから渡されたデータを使う宣言 */
for (QListIterator<Blog> it(blogList); it.hasNext(); ) {
const Blog &i = it.next(); /* i は Blog オブジェクトの参照 */
%% /* ループ(for文)するときのお決まりで、その要素と子要素を繰り返す */
}
@id ~= i.id() /* @id というマークのある要素のコンテントに i.id() の結果を入れる */
@title ~= i.title()
@body ~= i.body()
@linkToShow :== linkTo("Show", urla("show", i.id())) /* その要素と子要素を linkTo() の結果で置き換える */
@linkToEdit :== linkTo("Edit", urla("edit", i.id()))
@linkToRemove :== linkTo("Remove", urla("remove", i.id()), Tf::Post, "confirm('Are you sure?')")
簡単に Otama 演算子を説明します。
~ (チルダ)は、右辺の結果を、マークされた要素のコンテントに設定します。
= は、HTML エスケープして出力します。
従って、~= は右辺の結果を HTML エスケープし、要素のコンテントに設定します。HTML エスケープしたくなかったら、~== を使います。
また、: (コロン)は、マークされた要素および子要素をその右辺の結果で置き換えます。従って、:== は HTML エスケープせずに要素を置き換えます。
サービスまたはコントローラからビューへのデータの引き渡し
サービスで texport されたデータ(オブジェクト)をビューで使う場合は、tfetch メソッドで宣言する必要があります。引数には、変数の型と変数名を指定します。すると、指定された変数は texport される直前の状態と同じになるので、通常の変数と全く同じように使えます。上記のプレゼンテーションロジックの中で、実際そのように使われてます。
使い方の例:
サービス側:
int foo;
foo = ...
texport(foo);
ビュー側:
tfetch(int, foo);
※ 2つ以上の変数に対しそれぞれ texport をコールすれば、ビューに引き渡すことができます
Otama システムは、これらテンプレートファイルとプレゼンテーションファイルを元に C++ コードを生成します。内部的には、tmake がそれを処理しています。その後、コードはコンパイルされ、ビューとして1つの共有ライブラリになります。なので、動作は非常に高速です。
HTML 用語解説
要素(element)は、開始タグ (Start-tag)、コンテント (Content)、終了タグ (End-tag) の 3 つで構成されます。例として “<p>Hello</p>” という要素があったとすると、<p> が開始タグ、Hello がコンテント、</p> が終了タグになります。
一般にコンテントのことを「内容」と呼ぶことの方が多いようですが、個人的に少々紛らわしいと思うので、ここではコンテントと書いています。
モデルと ORM
TreeFrog では、モデルオブジェクトは永続化可能な概念を表現したデータの実体であり、ORM オブジェクトの小さいラッパーです。モデルが ORM オブジェクトを含むという関係なので、has-a の関係です(ただし、2つ以上の ORM オブジェクトを持つようなモデルを作っても構いません)。他のほとんどのフレームワークでは、デフォルトで 「ORM オブジェクト=モデル」 になっていますから、ここは少し違っていますね。
TreeFrog には SqlObject という名の O/R マッパーがデフォルトで組み込まれています。
C++ は静的型付け言語なので型の宣言が必要です。生成された SqlObject ファイル blogobject.h を見てみましょう。
半分ほどおまじないコードがありますが、テーブルのフィールドがパブリックなメンバ変数として宣言されています。構造体に近いですね。たったこれだけで、CRUD 相当のメソッド(create, findFirst, update, remove) が使えるようになります。それらのメソッドは TSqlObject クラスと TSqlORMapper クラスに定義されています。
class T_MODEL_EXPORT BlogObject : public TSqlObject, public QSharedData
{
public:
int id {0};
QString title;
QString body;
QDateTime created_at;
QDateTime updated_at;
int lock_revision {0};
enum PropertyIndex {
Id = 0,
Title,
Body,
CreatedAt,
UpdatedAt,
LockRevision,
};
int primaryKeyIndex() const override { return Id; }
int autoValueIndex() const override { return Id; }
QString tableName() const override { return QLatin1String("blog"); }
private: /*** Don't modify below this line ***/ // ここから下はおまじないマクロ
Q_OBJECT
Q_PROPERTY(int id READ getid WRITE setid)
T_DEFINE_PROPERTY(int, id)
Q_PROPERTY(QString title READ gettitle WRITE settitle)
T_DEFINE_PROPERTY(QString, title)
Q_PROPERTY(QString body READ getbody WRITE setbody)
T_DEFINE_PROPERTY(QString, body)
Q_PROPERTY(QDateTime created_at READ getcreated_at WRITE setcreated_at)
T_DEFINE_PROPERTY(QDateTime, created_at)
Q_PROPERTY(QDateTime updated_at READ getupdated_at WRITE setupdated_at)
T_DEFINE_PROPERTY(QDateTime, updated_at)
Q_PROPERTY(int lock_revision READ getlock_revision WRITE setlock_revision)
T_DEFINE_PROPERTY(int, lock_revision)
};
TreeFrog の O/R マッパーにはプライマリキーでの照会や更新を行うメソッドがありますが、SqlObject が持てるプライマリキーは primaryKeyIndex() メソッドで返す1つだけです。従って、複数プライマリキーをもつテーブルでは、必要に応じて修正し1つ返してください。
TCriteria クラスを使うことで、より複雑な条件を指定してクエリを発行することも可能です。詳しくは各章で。
次に、モデルを見てみましょう。
各プロパティのセッター/ゲッターと、オブジェクトの生成/取得の静的メソッドが定義されています。親クラスの TAbstractModel に保存 (save) と削除 (remove) のメソッドが定義されているので、結果として Blog クラスには CRUD 相当のメソッド(create, get, save, remove)が備わっています。
class T_MODEL_EXPORT Blog : public TAbstractModel
{
public:
Blog();
Blog(const Blog &other);
Blog(const BlogObject &object); // ORM オブジェクト指定のコンストラクタ
~Blog();
int id() const; // ここからセッター、ゲッターが並ぶ
QString title() const;
void setTitle(const QString &title);
QString body() const;
void setBody(const QString &body);
QDateTime createdAt() const;
QDateTime updatedAt() const;
int lockRevision() const;
Blog &operator=(const Blog &other);
bool create() { return TAbstractModel::create(); }
bool update() { return TAbstractModel::update(); }
bool save() { return TAbstractModel::save(); }
bool remove() { return TAbstractModel::remove(); }
static Blog create(const QString &title, const QString &body); // オブジェクト生成
static Blog create(const QVariantMap &values); // Hash でプロパティを渡してオブジェクト生成
static Blog get(int id); // ID 指定でモデルオブジェクトを取得
static Blog get(int id, int lockRevision); // ID とlockRevision指定でモデルオブジェクトを取得
static int count(); // ブログデータアイテムの量
static QList<Blog> getAll(); // モデルオブジェクトを全取得
static QJsonArray getAllJson(); // JSONスタイルにモデルオブジェクトを全取得
private:
QSharedDataPointer<BlogObject> d; // ORM オブジェクトのポインタを持つ
TModelObject *modelData();
const TModelObject *modelData() const;
};
Q_DECLARE_METATYPE(Blog) // おまじない
Q_DECLARE_METATYPE(QList<Blog>)
ジェネレータで自動生成されたコードはそんなに多くないステップ数にも関わらず、基本的な機能ができあがっています。
当然ながら生成されたコードは完全ではなく、実際のアプリケーションではさらに複雑な処理になるはずなので、そのままは使えないことが多いかもしれません。手直しが必要でしょう。ジェネレータは、コードを書く手間を少々省く程度のものと考えてください。
上記で説明したコードの裏では、クッキーの改ざんチェック、楽観的ロック、SQL インジェクション対策や認証トークンを使った CSRF 対策が機能しています。興味のある方、ソースをのぞいてみてください。