MoorでのForeign Key設定方法

FlutterのデータベースライブラリのMoorを選択する方がいると思いますが、今記事では、どのように"Foreign Key"を設定するのか、1対多、多対多はMoorでどう実現するのかを見ていきます。

対象読者
* moorの設定方法がある程度理解できている人。
* moorのテーブル作成をしたことがある人。
* daoでクエリを記述したことのある人。(知らなくても大丈夫かも)
* 外部キーの方法で困っている人。


※今回は、基本的なmoorのことは省いています。(コード自動生成など)

## 1. データベース設定
初めにデータベースの設定を行なっていきます。
この設定は、moorの公式ホームページを見てもらったら簡単に設定できると思います。
[Moor公式HP](https://moor.simonbinder.eu/docs/getting-started/)

```dart:database.dart
@UseMoor(tables: [CategoryRecords, SubCategoryRecords], daos: [DaoQuery])
class MyDatabase extends _$MyDatabase {
  MyDatabase() : super(_openConnection());

  @override
  int get schemaVersion => 1;

   @override
  　MigrationStrategy get migration => MigrationStrategy(
      onCreate: (detail) {
　　　　　　　　　　　　　　//一番初めにアプリを明け、データベースにアクセスするタイミングで呼ばれる。
      　//この記述なくても発火してくれるため、書く必要なし。
        return detail.createAll();
      },
      //毎回アプリを明けデータベースにアクセスするタイミングで呼ばれる。
      beforeOpen: (details) async {
        await customStatement('PRAGMA foreign_key = ON');
      }
      });
}

LazyDatabase _openConnection() {

  return LazyDatabase(() async {

    final dbFolder = await getApplicationDocumentsDirectory();
    final file = File(p.join(dbFolder.path, 'db.sqlite'));
    return VmDatabase(file);
  });
}
```

moorに慣れている人にはお馴染みのコードですが、MigrationStrategyなるものが追加されています。
onCreate関数は、「1番初めのアプリ起動時かつ、データベースに初めてアクセスするタイミング」で呼ばれます。なので、一度アプリをキルし、2度目アプリを開き、初めてデータベースにアクセスした際は呼ばれません。

beforeOpen関数は、「アプリを起動しデータベースに初めてアクセスするタイミング」で必ず呼ばれるものになります。
この関数の場合は、一度アプリをキルし、2度目アプリを開き、初めてデータベースにアクセスした際は呼ばれます。

beforeOpen関数内で、

```dart
customStatement('PRAGMA foreign_key = ON');
```
との記述があります。
sqliteはデフォルトで外部キーが設定されていない(OFF)ようなので、このデータベースに初めてアクセスするタイミングで、外部キー制約をONにしなければいけないようです。

これで、下準備は完了です。

次に実際にテーブルを記述していきます。

## 2. テーブル作成。
今回は、
主テーブル: CategoryRecords
小テーブル: SubCategoryRecords
の二つのテーブルを作成し、1対多の関係を作っていきます。

```dart:category_records.dart
class CategoryRecords extends Table {
  IntColumn get id => integer().autoIncrement()();

  TextColumn get name => text().withLength(max: 100)();

  RealColumn get experience => real()();
}
```

```dart:sub_category_records.dart
class SubCategoryRecords extends Table {
  IntColumn get id => integer().autoIncrement()();

  TextColumn get name => text().withLength(max: 100)();

  IntColumn get level => integer()();

  BoolColumn get nowLevel => boolean()();

  IntColumn get categoryId =>
      integer().customConstraint('NOT NULL REFERENCES categoryRecords(id)')();
}
```

SubCategoryRecordテーブルに外部キーを設定しています。
moor自体には、dartのコードで外部キー制約することができないみたいなので、

```dart
integer.customConstraint('NOT NULL REFERENCES categoryRecords(id)')();
```

のように、customConstraintを使用して、独自で外部キー制約をカスタムしないといけないようです。

これで、SubCategoryRecordsは、CategoryRecordsのidを外部キーとしてもつことができました。

その後、コード生成してもらえたら大丈夫です。

## 3,データ取得クエリ(例)
それでは、コード生成し終え、テーブル内にすでにデータが入っていることを想定して2つのテーブルをjoinしデータを取得する方法を見ていきましょう。

クエリは、daoを使用し別Classで記述しています。



```dart:dao_query.dart

import 'package:demo_flutter_project/models/db/practice/database.dart';
import 'package:demo_flutter_project/models/db/practice/tables/category_records.dart';
import 'package:demo_flutter_project/models/db/practice/tables/sub_category_records.dart';
import 'package:moor/moor.dart';

part 'dao_query.g.dart';

//2つのテーブルから取得できたデータを格納するClassを作成。
class MainAndSubCategory {
  final CategoryRecord categoryRecord;
  final SubCategoryRecord subCategoryRecord;

  MainAndSubCategory(this.categoryRecord, this.subCategoryRecord);
}

class DaoQuery extends DatabaseAccessor<MyDatabase> with _$DaoQueryMixin {
  DaoQuery(MyDatabase db) : super(db);

  //2つのテーブルをjoinしてデータを取得する方法。
  Future<List<MainAndSubCategory>> getAllMainAndSubCategory() async {
    //subCategoryRecordsテーブルを軸にjoinメソッドでcategoryRecordsをjoinする。
    final query = select(subCategoryRecords).join([
      //categoryRecordsのidとsubCategoryRecordsのcategoryIdが一致しているものに対して。
      //leftOuterとrightOuterも用意されている。
      innerJoin(categoryRecords,
          categoryRecords.id.equalsExp(subCategoryRecords.categoryId))
    ]);
　　　　　　　　
　　　　　　　　　//queryを使用し、getでデータ取得。(get()もしくは、watch()でデータを取得可能。)
    final typedResult = await query.get();
    final List<MainAndSubCategory> categoryList = [];
    //mapではできなかった。(理由は詳しい方教えてください。。。)
    for (var result in typedResult) {
      categoryList.add(MainAndSubCategory(
          //以下のように、それぞれのテーブルから取得してきたデータを取得できる。
          result.readTable(categoryRecords),
          result.readTable(subCategoryRecords)));
    }
    return categoryList;
  }
}
```

クエリの記述方法はこんな形になります。
queryを使用してgetする。。。
あたりからは、人によって記述方法が変わってくると思います。

今回は、get()で取得したのですが、watchでもデータ取得できますが、[公式](https://moor.simonbinder.eu/docs/advanced-features/joins/)にガッツリ方法が記述されているので今回はget()で記述しました。

## 終わり
今回はmoorを使用して外部キー制約を見ていきました。
自分もmoorはあまり触ったことがなく、間違っている部分があると思いますのでご指摘お願いいたします。

また、seed値のようなものをmoorで作成できる方法があればご教授願いたいです。


ありがとうございました。

### 参考文献
https://www.youtube.com/watch?v=RsXZOuWAAVM