モデル

モデルとはブラウザに返す内容(抽象的な情報)を表現するオブジェクトである、と理解することにします 。実際にはビジネスロジックも入ってくるわけで、そう単純ではありませんが。

モデルが持つ状態はデータベースあるいは外部のシステムに保存されます(永続化される)。コントローラ側から見れば、モデルがデータベースにアクセスしているかどうかは関係なく、単に HTTP レスポンスのための情報がほしいだけです。残すべき情報をデータベースへ永続化したら、あとはビューへ受け渡すための情報を持つモデルを取得できればいいわけです。

データベースへアクセスするのに直接 SQL を記述すると、時として煩雑なコードになってしまい、読みづらくなる傾向があります。DBスキーマが複雑になればなるほどこの傾向は強くなります。この煩雑さを軽減するために、TreeFrog には O/R マッピングシステム(SqlObject と名付けた)が搭載されています。

Webアプリにおいて、CRUD(生成、読取、更新、削除)は必要最低限の機能であり、書くSQL文はほとんど決まりきっています。この部分に対して、O/R マッピングシステムは特に有効に機能することでしょう。

多くの Web フレームワークでは、モデルオブジェクト自体が O/R マッピングのためのオブジェクト(ORM オブジェクトと呼ぶことにする)になることが推奨されているように思いますが、TreeFrog Framework でのデフォルトはモデルオブジェクトが ORM オブジェクトを含むという has-a の関係になっています。

” Model has ORM-object(s). “

このようにしたのは、O/R マッピングシステム自体をコンパクトにしたかったというのが理由の1つですが、他にはこんなメリットがあるのではないでしょうか。

  • ORM クラスの構造が直感的に分かる → 見た目は構造体
  • コントローラ側に見せる必要のない情報を隠蔽することができる
  • レコードの論理削除と物理削除をモデルで吸収できる →削除フラグのカラムを作成しておき、モデルの remove() メソッドの中でそれを更新すればよい
  • モデルとテーブルの関係が1対1である必要がないので、モデルクラス設計の自由度が増す → 自然な形でビジネスロジックをモデルに追加できる

デメリットは、少々コード量が増えると言うことでしょうか。

結論: コントローラとビューに不要な情報は隠蔽せよ。

モデルのAPI

一般に、クラスは独立性を高めた方が再利用性があがるわけで、同様にモデルもできる限り他との依存性が小さくなるように設計するのが望ましいでしょう。

Webアプリではデータを永続化する場合にDBがよく使われるので、モデルはテーブルと関連づきます。多くの種類のデータを扱うWebアプリは、それに応じて多くのテーブル(モデル)が作成されます。DBスキーマを設計する際、データの正規化を行うのが一般的なので、モデル同士はプロパティ(フィールド)を通じてリレーションをもつことになります。

モデルのクラスをコーディングするとき、次のようなお作法がありますので、ぜひならってください。

texport メソッドでエクスポート可能にする (ビューへ受け渡すことができる)。 これは QVariant 型に設定可能(setValue できる)であるのと同じことであり、次を備えたクラスのことです。

  • パブリックなデフォルトコンストラクタがある
  • パブリックなコピーコンストラクタがある
  • パブリックなデストラクタがある
  • Q_DECLARE_METATYPE マクロで宣言する(ヘッダファイルの末尾あたりに)

詳細を知りたい方は Qt ドキュメントをご覧ください。

ジェネレータコマンドで生成されたモデルはこれらを満たしています

ジェネレータで作成されたモデルクラスは TAbstractModel クラスを継承していますが、これには ORM オブジェクトを扱う便利なメソッドが実装されており、その機能を利用するために継承しているのです。つまり、単なる機能再利用のための継承です。従って、データベースにアクセスしないモデルでは、必要のない継承です。

結論: ORM オブジェクトを使うなら TAbstractModel クラスを継承せよ。

ジェネレータでモデルを生成すると、各プロパティのゲッター・セッターに加え、生成(C) および読込(R) に相当するクラスメソッド(静的メソッド)が定義されます。次の例はチュートリアルの章で作った Blog クラスの抜粋です。

static Blog create(const QString &title, const QString &body);
static Blog create(const QVariantMap &values);
static Blog get(int id);        // ID 指定でモデルオブジェクトを取得
static Blog get(int id, int lockRevision);
static QList<Blog> getAll();    // モデルオブジェクトを全取得

この create メソッドを実行すると、オブジェクトの生成と同時にその内容がデータベースに保存されます。

TAbstractModel クラスに定義されたメソッドも見てみましょう。

virtual bool create();         // 新規作成
virtual bool save();           // 保存(新規作成 or 更新)
virtual bool update();         // 更新
virtual bool remove();         // 削除
virtual bool isNull() const;   // DBに存在するかどうか
virtual bool isNew() const;    // DBに保存前かどうか
virtual bool isSaved() const;  // DBに保存されているかどうか
void setProperties(const QVariantMap &properties);

save メソッドは、ORM オブジェクトが存在していなければ create メソッドを、存在していれば update メソッドを内部で呼び出します。モデルを呼び出す側として新規作成と更新とでメソッドを区別したくなければ、この save メソッドを使うことができるでしょう。

ここで生成されたコードはとっかかりにしかすぎません。 隠蔽すべきプロパティは protected か private に移したり、要件に応じて好きなように追加・修正を行ってください。

テーブル名とは違う名前のモデルを作成する

ジェネレータコマンドでモデルを作成すると、モデル名はテーブル名から’_‘(アンダースコア)を抜いた形式になります。この形式とは違う名前を個別に付ける場合は、次のように末尾にその文字列を指定してコマンドを実行します。

 $ tspawn  model  blog_entries  BlogEntry    ← モデルのみ作成
 $ tspawn  s  blog_entries  BlogEntry   ←  モデル・ビュー・コントローラ作成

独自モデルの作成

モデルはテーブルに必ずしも関連づける必要はありません。ビューに情報を渡すために、関連するデータをまとめるケースにも使われます。

ジェネレータを使用せずに独自にモデルを作成する場合は、以下の例のようにクラスを宣言してください。models ディレクトリに保存し、ヘッダとソースのファイル名をプロジェクトフ ァイル(models.pro)に追加します。あとは make を実行してください。

class T_MODEL_EXPORT Post
{
    public:
    // デフォルトコンストラクタ、コピーコンストラクタ、デストラクタを含めます。
    // あとは自由にコードを書きます。
};
Q_DECLARE_METATYPE(Post)        // ビューに渡すためのおまじない
Q_DECLARE_METATYPE(QList<Post>) // リストをビューに渡すためのおまじない