QuasarFramework(Vue.js)で、Firebase Realtime Database を使ってみる。


# 前提
Vue.js, QuasarFrameworkなどで、Firebaseに作成しているアプリなどがある。
まだの方は、以下の記事など参照にしていただけますと幸いです。
https://qiita.com/yassyskywalker/items/d74ea3b0834145df5473

# FirebaseUser新規作成時にセットされるプロパティの確認
Firebase のAuthenticationでセットするUserは、以下のプロパティを持っているようです。

![1_user作成でサポートされているプロパティ.png](https://qiita-image-store.s3.amazonaws.com/0/125574/ecca3e11-00bd-2e7a-319e-d6088a3318f4.png)


更新時はこちら

![2_user更新でサポートされているプロパティ.png](https://qiita-image-store.s3.amazonaws.com/0/125574/01d95485-329e-22f2-d48d-4f4f846e0400.png)


#  Firebase Realtime DatabaseにUserのプロパティを追加する。

> Firebase User オブジェクトに他のプロパティを直接追加することはできませんが、代わりに Firebase Realtime Database に追加プロパティを格納することができます。

とのことなので、サインアップのときに、一緒にRealtime Databaseに、Userテーブルを作成してみようと思います。

## サインアップ時に、Realtime Database Userを作成する

参考:
https://firebase.google.com/docs/database/security/?hl=ja

目標とするデータベース構造は以下のようなものです。

```json
{
  "users": {
    "<uid>(Authentication UserのID)": {
      "name": "山田　太郎",
      "username": "taro",
      "homepage": "https://www.test",
      "comment": "よろしく",
      "tel": "123456",
      "sex": "female",
      "public": true
    },
  }
}
```

サインアップ時に、uidとusernameだけを設定した空のデータをFRDに作っておきます。

```html:Signup.vue
<template lang="html">
  <div>
    <div class="layout-padding row justify-center background-whitesmoke items-center">
      <div class="col-xs-12 col-sm-7 col-lg-5">
        <q-card class="q-card-background-white">
          <q-card-main>
            <div class="row">
              <div class="col-sm-10 offset-sm-1 text-center">
                <img src="~assets/quasar-logo-full.svg" class="logo-sized">
              </div>
            </div>
            <div class="row">
              <div class="col-sm-10 offset-sm-1 text-left">
                <h5>新規登録</h5>
              </div>
            </div>
            <div class="row">
              <div class="col-sm-10 offset-sm-1 text-left">
                <p style="font-size: 12px;">新規アカウントの登録をお願いします。</p>
              </div>
            </div>
            <div class="row">
              <div class="col-sm-10 offset-sm-1">
                <q-field>
                  <q-input
                    v-model="username"
                    float-label="ユーザー名"
                    color="primary"
                    type="text"
                    pattern="^[0-9A-Za-z]+$"/>
                </q-field>
              </div>
            </div>
            <div class="row">
              <div class="col-sm-10 offset-sm-1">
                <q-field>
                  <q-input v-model="email" float-label="メールアドレス" color="primary" type="email"/>
                </q-field>
              </div>
            </div>
            <div class="row">
              <div class="col-sm-10 offset-sm-1">
                <q-field>
                  <q-input v-model="password" float-label="パスワード" color="primary" type="password"/>
                </q-field>
              </div>
            </div>
            <div class="row" style="padding-top: 20px;">
              <div class="col-sm-10 offset-sm-1 text-right">
                <q-btn color="primary" style="font-weight: bold;" @click="signUp">新規登録</q-btn>
              </div>
            </div>
            <br>
            <div class="row">
              <p style="color:#027be3;font-size:12px;"><router-link :to="{ name: 'Login' }">すでに登録済みの方はサインインしてください</router-link></p>
            </div>
          </q-card-main>
        </q-card>
      </div>
    </div>
  </div>
</template>

<script>
import {
  QBtn,
  QCard,
  QCardMain,
  QField,
  QInput
} from 'quasar'
import firebase from 'firebase'

export default {
  name: 'signUp',
  components: {
    QBtn,
    QCard,
    QCardMain,
    QField,
    QInput
  },
  data () {
    return {
      username: '',
      password: '',
      email: '',
      user_uid: '',
      newUser: {
        name: '',
        username: '',
        homepage: '',
        comment: '',
        tel: '',
        sex: '',
        public: true
      }
    }
  },
  methods: {
    signUp () {
      firebase.auth().createUserWithEmailAndPassword(this.email, this.password).then(
        (user) => {
          this.createNewUser(user)
          // Emailの確認もつけておく
          user.sendEmailVerification().then(
            () => {
              alert('アカウントの新規作成が完了しました！')
            },
            (err) => {
              alert('EmailVerificationでerrが発生しました。', err)
            }
          )
          this.$router.replace('/')
        },
        (err) => {
          let errorCode = err.code
          let errorMessage = err.message
          alert(errorCode, errorMessage)
        }
      )
    },
    createNewUser (user) {
      // UserのUIDをPKにして、users テーブルを作成する
      const uid = user.uid
      const username = this.username
      this.newUser.username = username
      firebase.database().ref('users/' + uid).set(this.newUser)
    }
  }
}
</script>

<style lang="stylus">
.q-card-background-white
  background white

.background-whitesmoke
  background whitesmoke
  height 100vh

.logo-sized
  width 150px
  margin 20px
</style>
```

登録を実行して、Realtime Database　にusers/ のテーブルができていれば成功です！

## Realtime Database に, rulesを追加する。
ルールをカスタマイズしてセキュリティを高めます。公式サイトを参考にします。
https://firebase.google.com/docs/database/security/?hl=ja
https://firebase.google.com/docs/database/security/user-security?hl=ja
https://firebase.google.com/docs/database/web/read-and-write?authuser=1

> Firebase Database ルールの構文は JavaScript ライクで、次に示す4つの種類があります。
> .read	ユーザーによるデータの読み取りを許可するときに記述します。
> .write	ユーザーによるデータの書き込みを許可するときに記述します。
> .validate	値の適切なフォーマット方法、子属性を持つかどうか、およびデータ型を定義します。
> .indexOn	インデックスを作成する子に並べ替えとクエリをサポートするように指定します。

以下のようにコンソール画面のrulesで修正できます。

```json
{
  "rules": {
    "users": {
      "$uid": {
        ".write": "$uid === auth.uid",
        ".read": "$uid === auth.uid"
      }
    }
  }
}
```

さらに、`.validate`で、バリデーションも追加してみます。

```json
{
  "rules": {
    "users": {
      "$uid": {
        ".write": "$uid === auth.uid",
        ".read": "$uid === auth.uid",
        "comment": {
          ".validate": "newData.isString() && newData.val().length > 0 && newData.val() < 200"
        }      
      }
    }
  }
}
}
```

これで一旦サインアップのときに一緒にuser テーブルを作成する作業は完了です！

# Realtime Databaseの、Userデータを読み取り&更新する

以下のような感じでRealtime Databasから、データの読み込みができます。

```javascript
const db = firebase.database()
const dbProfileRef = db.ref('users/' + this.userUID)
dbProfileRef.on('value', snapshot => {
  console.log('snapshot.val()', snapshot.val())
})
```

データをプロフィールフォームに読み込んでみます。ここでは、テストで、createdのタイミングで、データベースからユーザー情報を読み込んでみます。
また、update()メソッドで、ユーザ情報を更新することもできます。

```html:MyPageProfile.vue
<template lang="html">
<div class="row">
  <div class="col-xs-12 col-sm-12">
    <!-- プロフィールフォーム -->
    <div class="row justify-center">
      <div class="col-6">
        <q-field
          label="名前"
        >
          <q-input v-model="name" type="text"/>
        </q-field>
      </div>
    </div>
    <div class="row justify-center">
      <div class="col-6">
        <q-field
          label="ユーザーネーム"
        >
          <q-input v-model="username" />
        </q-field>
      </div>
    </div>
    <div class="row justify-center">
      <div class="col-6">
        <q-field
          label="ホームページ"
        >
          <q-input v-model="homepage" type="url"/>
        </q-field>
      </div>
    </div>
    <div class="row justify-center">
      <div class="col-6">
        <q-field
          label="自己紹介"
          type="textarea"
        >
          <q-input v-model="comment" type="textarea" :min-rows="5" />
        </q-field>
      </div>
    </div>
    <br>
    <div class="row justify-center">
      <div class="col-6 text-center bold-underline">
        <span>非公開情報</span>
      </div>
    </div>
    <div class="row justify-center">
      <div class="col-6">
        <q-field
          label="電話番号"
        >
          <q-input v-model="tel" type="tel"/>
        </q-field>
      </div>
    </div>
    <div class="row justify-center">
      <div class="col-6">
        <q-field
          label="性別"
        >
          <q-select
            v-model="sex"
            :options="sexOptions"
          />
        </q-field>
      </div>
    </div>
    <div class="row justify-center">
      <div class="col-6">
        <q-field
          label="公開する"
        >
          <q-checkbox v-model="public" />
        </q-field>
      </div>
    </div>
    <div class="row justify-center">
      <div class="col-6 text-center">
        <q-btn color="primary" class="q-btn-margin" @click="updateUser">保存する</q-btn>
      </div>
    </div>
    </div>
  </div>
</div>
</template>

<script>
import {
  QBtn,
  QCheckbox,
  QField,
  QInput,
  QSelect
} from 'quasar'
import MyPageHeader from './MyPageHeader.vue'
import firebase from 'firebase'

export default {
  name: 'myPageProfile',
  components: {
    QBtn,
    QCheckbox,
    QField,
    QInput,
    QSelect
  },
  data () {
    return {
      url: '',
      sexOptions: [
        {
          label: '男性',
          value: 'male'
        },
        {
          label: '女性',
          value: 'female'
        }
      ],
      name: '',
      username: '',
      homepage: '',
      comment: '',
      public: false,
      tel: '',
      sex: ''
    }
  },
  computed: {
    userUID () {
      return this.$store.getters.userUID
    }
  },
  methods: {
    updateUser () {
      const userProfile = {
        name: this.name,
        username: this.username,
        homepage: this.homepage,
        comment: this.comment,
        public: this.public,
        tel: this.tel,
        sex: this.sex
      }
      firebase.database().ref('users/' + this.userUID).update(userProfile)
    }
  },
  created () {
    const db = firebase.database()
    const dbProfileRef = db.ref('users/' + this.userUID)
    dbProfileRef.on('value', snapshot => {
      snapshot.forEach((data) => {
        let keyName = data.key
        this.$data[keyName] = data.val()
      })
    })
  }
}
</script>

<style lang="stylus">
.profile-img
  width 100px
  margin 20px

.bold-underline
  font-weight bold
  text-decoration underline

.tab-pane
  margin 20px

.q-btn-margin
  margin 20px
  font-weight bold

.q-field-label-inner
  color black
  font-weight bold
</style>
```

以下の２点が実行されれば成功です！

* 読み込みのときに、データベースからデータが読み込まれる
* 更新して保存ボタンをクリックすると、データベースの値が更新される

# まとめ
いままでSQLしか触ったことが無かったので、最初はかなりとまどったのですが、Firebaseのドキュメントが充実しているおかげで、スムーズにNoSQLへ移項できそうな気がします。
Firebaseの日本語ドキュメントと、動画講義の充実度合いは素晴らしいですね。
かなりファンになりました。特にSQL使ったことある人向けのRealtime Databaseの入門・解説動画はめっちゃわかりやすかったです。
https://www.youtube.com/watch?v=WacqhiI-g_o