Sequelizeで多対多のテーブルジョインを定義する

一つの記事には複数の著者が存在し(共同著者)、著者には複数の記事が結びつく場合、中間テーブルを利用して多対多の状態をつくれるようにしなければならない。

そのように行う方法を説明する。

これには、 belongsToMany を利用することで多対多のデータ構造を作成することができる。 簡易的な書き方を紹介して、その次に、 asforeignKey を使って細かいことをできる指定の仕方を紹介する。

定義は以下。

Article.belongsToMany(author.factory(sequelize), {
  through: "ArticleAuthor"
});

使い方は以下。

const findAllIncludeParams: IncludeOptions[] = [];

findAllIncludeParams.push({
  model: Author,
  attributes: [],
  required: true
});

Article.findAndCountAll({
  include: findAllIncludeParams
})

これにもっと細かい指定をすると、以下のようになる。

もし、 Article-Author 間の連結が1種類でない場合は、 as で別名をつけることで、 複数の Article-Author の連結を行うことができる。

定義は以下。

Article.belongsToMany(author.factory(sequelize), {
  as: "essay",
  through: "articleAuthor",
  foreignKey: "articleId",
  otherKey: "authorId"
});
Article.belongsToMany(author.factory(sequelize), {
  as: "novel",
  through: "articleAuthor",
  foreignKey: "articleId",
  otherKey: "authorId"
});

使い方は以下。

findAllIncludeParams.push({
  as: "aaa",
  model: Author,
});

参考

sequelize.org

resolveJsonModuleでDate型を使いたい

resolveJsonModule では json を読みこみ、 typeof を与えてあげることで、モックデータから型を自動で生成してくれる。 しかしながら、 json を読み込む部分では、 Date 型を扱いたくても、 string と判定されてしまう。

{
  ...
  createdAt: "2000-01-01 00:00:00"
  ...
}

これを Date 型にするには、 json を読み込むのではなく、以下のような ts ファイルを読み込ませ、 mockTypeimport することで Date 型を認識させることができる。

export const mockType = {
  ...
  createdAt: new Date("2000-01-01 00:00:00")
  ...
}

firebaseのチャットで画像を扱うために

LINEのようなチャットツールでは、チャットしている時系列に画像を表示したり、それとは別に、写真だけを一覧表示することができる。

これがRDBMSでは頭を捻らなくても実装することができるが、firebaseのようなNoSQLでこれを実装するためにはどのようにすれば良いか。

その場合は、 collectionGroup() を使用してすることで実装することができる。

そのために以下のようなコレクションを定義する。

※C: コレクション ※d: ドキュメント

rooms(C)
  - room1(d)
    - name
    - description
    - messages(C)
      - XXX(d)
        - body
          - images(C)
    - members(C)
      - member1(d)
        - name
        - photoUrl

写真を時系列のメッセージと共に流す際には、

db.collection("rooms").doc("room1").collection("messages")

のように普通通りに取得をして、画像だけを一覧したい場合は

db.collection("rooms").doc("room1")collectionGroup("images")

を実行する。

collectionGroupを使うことで、他のmessagesドキュメントの写真を串刺しにして取得することができる。

SequelizeでwhereHasを行う

include内のrequired値をtrueにすることでwhereHasと同じ挙動の動作をさせることができる。 falseの場合は、ORMのリレーションの結果が0件でも取得できる。

attributesを[]にすることで、whereHasだけを利用することも可能。

XXX.findAll({
    where: findAllParams,
    include: [
        {
            model: YYY,
            where: { YYYId: 3 },
            attributes: ['YYYId'], // カラムの絞り込み
            required: true
        }
    ],
    limit: limit,
    offset: offset
})

firebaseのAuthenticationとfirestoreを利用したセキュリティルール設定

ここではセキュリティルールを考えた際にfirestoreはどういったデータ構造にするか、ということを説明する。

詳細の設定については、 公式ブログ に譲る。

チャットアプリのような双方向の通信を実現する際は、 firebase を利用することで簡単に実装できる。

実際には、アプリケーション内で firestore のデータを監視して、データの追加/変更/削除が発生したら自身で用意したイベントを呼び出すことで、相手の 発信 を受け取ることができる。

ただ、 firestore は正しいセキュリティルールを設定しないと、 誰でも ストレージにアクセスができるので、必ずルールを設定しなければいけない。

ここで、 firestore を以下のように組み立てるとする(これは後に困るやり方)。

Collection Document Collection/data Document
chatRoom XXX member(C) YYY
message(C) ZZZ
name(d)
description(d)

アクセス制御を行うには、以下のような設定を行う。

match /chatRoom/{roomId} {
      allow read, write: if exists(/databases/$(database)/documents/chatRoom/$(roomId)/member/$(request.auth.uid));
}
// 上記以外全て拒否

このようにすることで、 /chatRoom にアクセスして全データを取得するときに、 /chatRoom/{roomId} の条件にヒットしないものは取得されない、と思ったが、このパス /chatRoom/{roomId} はリクエストパスを表現しているため、 このようにアクセス制御を行うと、 /chatRoom へのルールが設定されていないため、アクセス自体が拒否されてしまう。 /chatRoom の全取得で権限があるデータだけを取得する、という操作はできないことがわかる。

このことから、 firestore のデータ構造を以下のようにする必要がある。user コレクションには、 chatRoom の情報を非正規化して保持している。ただ、非正規化が行われるということは、チャットルーム名の変更などが発生した場合は、非正規化が発生した部分を全て変更しなければならない。firebaseでは、 collectionGroup() というメソッドでサブコレクションを串刺しにして取得することができるので、collectionGroup(YYY) とすることで、非正規化されたデータを一括で書き換えることができるようになる。

(※ user のサブコレクションの userRoomroom にしなかった理由はサブコレクションを串刺しにしたいときに、 room は他のサブコレクション名とバッティングする恐れがあるため、 userRoom とした)

Collection Document Collection/data Document data
chatRoom XXX message(C) ZZZ
name(d)
description(d)
user XXX userRoom(C) YYY name(d)
description(d)

このようにすることで、チャット一覧の取得は、 /user/{user}/userRoom で取得するようになる。このとき、認証されたユーザしか /user/{user}/userRoom のデータにアクセスできない。チャットの詳細を取得するときは、 /chatRoom/{roomId} にアクセスして、認証されているユーザがこのルームへのアクセス権限があるかを判定している。

match /user/{user}/userRoom {
      allow read, write: if user == request.auth.uid;
}
match /chatRoom/{roomId} {
      allow read, write: if exists(/databases/$(database)/documents/user/$(request.auth.uid)/userRoom/$(roomId));
}
// 上記以外全て拒否

これでセキュアな状態(非正規化ではあるもののデータ更新に困らない)のモノになるのではないだろうか。

H2Oを利用してFlaskをFastCGIとして動かすまで

とりあえず、Flaskは動くことを前提としている。

Flaskが動く環境が整ったら、UNIXドメインソケットを利用してFastCGIデーモンを動かすようにしてあげる。

UNIXドメインソケットは、以下のようにすることで作成される。以下をxxx.fcgiと、自分で名前を付けてFlaskのプロジェクトの最上位に置く(最上位でなくてもよいが)。 これは公式ドキュメントに書いている。

#!/usr/bin/python3
from flup.server.fcgi import WSGIServer
from yourapplication import app

if __name__ == '__main__':
    WSGIServer(app, bindAddress='/tmp/fcgi.sock').run()

この際に、xxx.fcgiのシバンの指定しているpythonの記載についpython3を使いたければpython3を指定し、2系を利用したければpythonでよい。 また、私のDockerの環境だと、python/usr/local/binに入っていて、公式ドキュメントのシバンでは参照できなかったので、which python3でシバンを正しく設定してあげる。

次に、FastCGIデーモンを動かす。デーモンとして動かすので、screenコマンドを使えるようにする。 ところで、デーモンとして動かす前に、FastCGIとして動かすためには、以下のコマンドで動かすことができる。

/yourapplication/xxx.fcgi

これで、動くのだが、私のDockerの環境では(bashではなくshだからか?)以下のようにしなければ動かなかったので注意。

python3 /yourapplication/xxx.fcgi

上記を利用して、screenでデーモンとして実行してあげればOK。

H2O側のFastCGIを待ち受けるソケットへの接続は以下でOK。

listen:
  port: 8080
user: root

hosts:
  "localhost":
    paths:
      /:
        fastcgi.connect:
          port: /tmp/fcgi.sock
          type: unix

access-log: /app/logs/access-log
error-log: /app/logs/error-log                          

これで、127.0.0.1:8080へアクセスすれば、UNIXソケットを利用してFlaskを動かすことができる。

ReactはNativeでtypescript対応したらしい

下記のQiita記事でReactをTSで開発する時の初期状態を作るコマンドが乗っていた。

ReactとTSの環境を整えるにはReactやTSのパッケージをいれたり、としないといけなかったのに、コマンド一つでプロジェクトを作成することができ、随分と楽になったんだなぁ、と思った。

qiita.com

そこでは、以下のコマンドを実行する、と書いていた。

npx create-react-app rg2-front --scripts-version=react-scripts-ts

しかし、実行してみると、以下のように言われた。

The react-scripts-ts package is deprecated. TypeScript is now supported natively in Create React App. You can use the
 --typescript option instead when generating your app to include TypeScript support. Would you like to continue using r
eact-scripts-ts

なんと、NativeでTS対応をしていると。

以下のコマンドでプロジェクトを作った。良い時代になったな、と思った。

npx create-react-app rg2-front --typescript