shivaq
8/30/2017 - 8:52 AM

▼ Firebase Realtime DB

▼ Firebase Realtime DB

▼ Firebase Realtime DB
-------------------------------------------------



▼ 設計なしで、いきなり値を置くことが可能
-------------------------------------------------
  private DatabaseReference databaseReference;
  private DatabaseReference helloEndpoint;
  private DatabaseReference worldEndpoint;

  private void someMethod(){
    // 下記3つとも DatabaseReference だが、ルート と その子 2つ という構成になる
    databaseReference = FirebaseDatabase.getInstance().getReference();
    helloEndpoint = databaseReference.child("Hello");
    worldEndpoint = databaseReference.child("World");

    helloEndpoint.setValue("Hello");
  }
-------------------------------------------------
▼ 上記コードで、下記 構成が生成される。これが、 NoSQL
-------------------------------------------------
ruten-b9288{
     Hello:"Hello"
}
-------------------------------------------------






▼ 一連のシークエンスを DB 保存するにはどうする?
-------------------------------------------------
案1:
ArrayList だけのためのテーブルを作り、他のテーブルから参照する

案2:
Gson を使う

 - 格納時
-------------------------------------------------
ArrayList<String> inputArray=new ArrayList<String>();

Gson gson = new Gson();

String inputString= gson.toJson(inputArray);
-------------------------------------------------

 - 読み込み時
-------------------------------------------------
Type type = new TypeToken<ArrayList<String>>() {}.getType();

ArrayList<String> finalOutputString = gson.fromJson(outputarray, type);
-------------------------------------------------












▼ Model 設計の制約のアイデア
-------------------------------------------------
Model 設計の制約

Model 名は単数形の名詞にする

 - Model 任意でKeyをセットする場合はSortできるキーをセットする
Firebase の性能はKeyで決まります。
Key は時系列を含み作成された順番を保っています。

Model は_updatedAt _createdAtを保持する
「いつ発生した」は、あらゆる問題を解決するための重要ないとぐち
-------------------------------------------------


▼ Model 内のプロパティにAccessKeyを含める
-------------------------------------------------
{
  user: {
    userID0: {
      activities: {
        activityID0: true,
        activityID1: true
      }
    }
  },
  activity: {
    activityID0: {
      userID: userID0  // activityID0にアクセスするためのAccessKey
      content: "hello",
      _createdAt: 012345678,
      _updatedAt: 012345678,
    }
  }
}
-------------------------------------------------
// Rule
{
    "rules": {
        "user": {
            "$user_id": {
                // 認証後、ログインユーザーと一致している場合データを読むことができる
                ".read": "auth != null && auth.uid == $user_id",
                // 認証後、ログインユーザーと一致している場合データを書き込むことができる
                ".write": "auth != null && auth.uid == $user_id"
            }
        },
        "activity": {
            "$activity_id": {
                // Userのactivitiesの中に$activity_idを保持していればデータを読むことができる
                ".read": "root.child('user/'+ auth.uid +'/activities/'+$activity_id).exists()"
                // このデータのuser_idがログインユーザーのIDと一致していればデータを読むことができる。
                ".read": "data.child('user_id').val() == auth.uid"
            }
        }
    }
}

-------------------------------------------------

▼ 各モデルが相互で参照を持つ構造は、Firebase Realtime DB でのModel設計の定石
-------------------------------------------------
{
    "group":{
        "0":{
            "name":"groupA"
            "users":{// 相互で参照を持つ →Gourp に所属する User を取得できるようになる
                "0":true,// users の id 0 と リレーションを持つ(true と表現)
                "1":true
            }
        },
        "1":{
            "name":"groupB"
            "users":{
                "2":true,
                "3":true
            }
        }
    },

    "user":{
        "0":{
            "groups":{// 相互で参照を持つ
                "0":true// groups の id 0 と リレーションを持つ
            },
            "name":"userA"
        },
        "1":{
            "groups":{
                "0":true
            },
            "name":"userB"
    },
        "2":{
            "groups":{
                "1":true
            },
            "name":"userC"
    },
        "3":{
            "groups":{
                "1":true
            },
            "name":"userD"
    }
}
-------------------------------------------------












▼ データは平坦にしてネストしないようにする。
// データベース内の特定の場所にあるデータをフェッチすると、そのすべての子ノードも取得されてしまうため
-------------------------------------------------
{
  // Chats → 各会話のメタ情報のみ内包
  "chats": {
    "one": {// chats の ユニークID としての one
      "title": "Historical Tech Pioneers",
      "lastMessage": "ghopper: Relay malfunction found. Cause: moth.",
      "timestamp": 1459361875666
    },
    "two": { ... },
    "three": { ... }
  },



  // chats と同じ階層。直下の chats ID を保持して相互参照。その配下に、members プロパティ
  "members": {
    // we'll talk about indices like this below
    "one": {
      "ghopper": true,
      "alovelace": true,
      "eclarke": true
    },
    "two": { ... },
    "three": { ... }
  },


  // chats と同じ階層。直下の chats ID を保持して相互参照。
  "messages": {
    "one": {
      "m1": {
        "name": "eclarke",
        "message": "The relay seems to be malfunctioning.",
        "timestamp": 1459361875337
      },
      "m2": { ... },
      "m3": { ... }
    },
    "two": { ... },
    "three": { ... }
  }
}
-------------------------------------------------


▼ データを拡張性高くする // M:N な構造
-------------------------------------------------
/users/$uid/groups/$group_id
 を読み取ってそれが null であることを確認するだけで、キーがないかのチェックが済む
 インデックスはデータのクエリやスキャンを実行するよりも迅速かつ効率的。
-------------------------------------------------
// An index to track Ada's memberships
{
  "users": {
    "alovelace": {// ID
      "name": "Ada Lovelace",
      // このユーザーのグループを、プロパティ内でインデックスする
      "groups": {
         "techpioneers": true,// IDの相互参照。IOW, グループをユーザーは以下でインデックス化するという言い方。
         "womentechmakers": true
      }
    },
    ...
  },


  "groups": {
    "techpioneers": {// ID
      "name": "Historical Tech Pioneers",
      "members": {
        "alovelace": true,// IDの相互参照。アイテム削除時は、双方を更新する必要がある。
        "ghopper": true,
        "eclarke": true
      }
    },
    ...
  }
}
-------------------------------------------------



▼ リンクテーブルみたいなのを作って、検索を簡素化するという方法
-------------------------------------------------
{
  "users": {
    "1": {
      "name": "David"
    },
    "9": {
      "name": "Alice"
    },
    "events":{
      "fm":{
        "name":"Firebase Meetup",
        "date":924654321
      }
    },
    "eventAttendees":{// users と events とのリンクブロック
      "fm":{// イベントのプライマリキー
        "1":"David",// users のプライマリキー:Name の key:value
        "9":"Alice"
      }
    }
  }
}
-------------------------------------------------
 エンドポイントをよういしておいて。。。
然るべきポイントで リンクブロックに追加
private DatabaseReference eventsEndpoint = dbRootReference.child("events/fm");
private DatabaseReference attendeeEndpoint = dbRootReference.child("eventAttendees/fm");
-------------------------------------------------





▼ denormalization 非正規化 (複数のパスに分割する) 平坦にする
-------------------------------------------------
{
  "chats": {// メタデータを持つ
    "one": {// chat のユニークID
      "title": "Historical Tech Pioneers",
      "lastMessage": "ghopper: Relay malfunction found. Cause: moth.",
      "timestamp": 1459361875666
    },
    "two": { ... },
    "three": { ... }
  },

  // Conversation members are easily accessible
  // and stored by chat conversation ID
  "members": {
    // we'll talk about indices like this below
    "one": {// chat のユニークID
      "ghopper": true,
      "alovelace": true,
      "eclarke": true
    },
    "two": { ... },
    "three": { ... }
  },

  // Messages are separate from data we may want to iterate quickly
  // but still easily paginated and queried, and organized by chat
  // conversation ID
  "messages": {
    "one": {
      "m1": {
        "name": "eclarke",
        "message": "The relay seems to be malfunctioning.",
        "timestamp": 1459361875337
      },
      "m2": { ... },
      "m3": { ... }
    },
    "two": { ... },
    "three": { ... }
  }
}
-------------------------------------------------

▼ インデックス化
-------------------------------------------------
/users/$uid/groups/$group_id これが null かどうかで、キーの有無をチェックできる
-------------------------------------------------
// An index to track Ada's memberships
{
  "users": {
    "alovelace": {
      "name": "Ada Lovelace",
      // Index Ada's groups in her profile
      "groups": {
         // the value here doesn't matter, just that the key exists
         "techpioneers": true,// users のこの ID が所属する groups
         "womentechmakers": true
      }
    },
    ...
  },
  "groups": {
    "techpioneers": {
      "name": "Historical Tech Pioneers",
      "members": {
        "alovelace": true,// groups のこの ID に属している users
        "ghopper": true,
        "eclarke": true
      }
    },
    ...
  }
}
-------------------------------------------------
















▼ ユーザーに紐付いた 値 を格納するために、ユーザー情報を取得して。。。
-------------------------------------------------
    // Disable button so there are no multi-posts
    setEditingEnabled(false);
    Toast.makeText(this, "Posting...", Toast.LENGTH_SHORT).show();

    // [START single_value_read]
    final String userId = getUid();// Auth から ID を取得
    mDatabase.child("users").child(userId).addListenerForSingleValueEvent(//users/userId/ から。。。
        new ValueEventListener() {
          @Override
          public void onDataChange(DataSnapshot dataSnapshot) {
            // Get user value
            User user = dataSnapshot.getValue(User.class);// Userにあたる値を取得

            // [START_EXCLUDE]
            if (user == null) {// 取得した値が nullでないのを確認して
              // User is null, error out
              Log.e(TAG, "User " + userId + " is unexpectedly null");
              Toast.makeText(NewPostActivity.this,
                  "Error: could not fetch user.",
                  Toast.LENGTH_SHORT).show();
            } else {
              // Write new post
              writeNewPost(userId, user.username, title, body);// ID、User 情報、今回書き込む内容を渡して。。。
            }

            // Finish this Activity, back to the stream
            setEditingEnabled(true);
            finish();
            // [END_EXCLUDE]
          }
-------------------------------------------------

▼ ユーザー限定パスと、全ユーザーの投稿用パスに、一度に 保存。
-------------------------------------------------
  // [START write_fan_out]
  private void writeNewPost(String userId, String username, String title, String body) {
    // Create new post at /user-posts/$userid/$postid and at
    // /posts/$postid simultaneously
    String key = mDatabase.child("posts").push().getKey();// 書き込みの ユニークID を取得し、
    Post post = new Post(userId, username, title, body);// Post モデルを作成し、
    Map<String, Object> postValues = post.toMap();// それを Map にする

    Map<String, Object> childUpdates = new HashMap<>();// 空の Map を作成し、
    // エンドポイントをキーに、Map を value で格納
    childUpdates.put("/posts/" + key, postValues);// 書き込み のパスに格納
    childUpdates.put("/user-posts/" + userId + "/" + key, postValues);// 固有のユーザーの書き込みのパスに格納

    mDatabase.updateChildren(childUpdates);// updateChildren() で、複数の子に格納
  }
  // [END write_fan_out]
-------------------------------------------------










▼ Listener のデタッチ
-------------------------------------------------
    @Override
    public void onStop() {
        super.onStop();

        // Remove post value event listener
        if (mPostListener != null) {
            mPostReference.removeEventListener(mPostListener);
        }

        // Clean up comments listener
        mAdapter.cleanupListener();
    }
-------------------------------------------------








▼ SQL のステートメントの、Firebase への置き換え

-------------------------------------------------
// 1. Select a user by UID
private DatabaseReference user1Ref = rootRef.child("users").child("1");

// 2. Find a user by email address
private DatabaseReference mailRef = rootRef.child("users").orderByChild("email").equalTo("alice@email.com");

// 3. Limit to 10 users
SELECT * FROM Users LIMIT 10;
private DatabaseReference limit10Ref = rootRef.child("users").limitToFirst(10);

// 4. Get all users names that start with "D"
SELECT * FROM Users WHERE Name LIKE 'D%';
private DatabaseReference limit10Ref = rootRef.child("users").orderByChild("name").startAt("D").endAt("D/uf8ff");

// 5. Get all users who are less than 50
SELECT * FROM Users WHERE Age < 50;
private DatabaseReference limit10Ref = rootRef.child("users").orderByChild("age").endAt(49);
private DatabaseReference limit10Ref = rootRef.child("users").orderByChild("age").startAt(51);//Age > 50
private DatabaseReference limit10Ref = rootRef.child("users").orderByChild("age").startAt(20).endAt(10);//20 >=Age >= 100

// 6. get all users who are 28 and Live in Berlin
SELECT * FROM Users WHERE Age = 28 && Location = "Berlin"
下記のように、age と location との組み合わせで index を作っておく
{
  "users": {
    "1": {
      "name": "David",
      "age":99,
      "location":"SF",
      "age_location":"99_SF"
    }
}
private DatabaseReference limit10Ref = rootRef.child("age_location").orderByChild("age").equalTo("99_SF");
-------------------------------------------------






▼ 読み取り //////////////////////////////////////////////////////////////////////








▼ 二つの Listener
-------------------------------------------------
アタッチした Listener の回数分、
removeEventListener() しましょう。
-------------------------------------------------

▼ ValueEventListener
-------------------------------------------------
特定のパスにあるコンテンツの静的スナップショットを、
イベントのときに存在していたとおりに読み取る
-------------------------------------------------

▼ ChildEventListener
-------------------------------------------------
ノードの 子 の追加や更新が トリガーとなる。特定の イベントに対する リッスン に使える
-------------------------------------------------




▼ 単発読み取り(リッスンなし)
-------------------------------------------------
final String userId = getUid();


mDatabase.child("users").child(userId)
.addListenerForSingleValueEvent(
    new ValueEventListener() {
      @Override
      public void onDataChange(DataSnapshot dataSnapshot) {
        // Get user value
        User user = dataSnapshot.getValue(User.class);

        // ...
      }

      @Override
      public void onCancelled(DatabaseError databaseError) {
        Log.w(TAG, "getUser:onCancelled", databaseError.toException());
      }
    });
-------------------------------------------------

















▼ ルール//////////////////////////////////////////////////////////////////////

▼ Advanced ルール
-------------------------------------------------
一部のユーザーに管理者アクセスを与えたり。
ユーザーが目標を達成したら、DB に格納された feature をアンロックしたり。メッセージを送ったり。
課金したときにのみ提供する featureなど。
-------------------------------------------------
例)課金者限定プライベートチャットルーム
1.スペシャルチャットのメッセージを含むメッセージのチャイルドを設定する
1.課金者のみが プライベートチャットルーム にアクセスできるというルールを設定
-------------------------------------------------
 - DB
{
// トップレベルノード 1
 "chat": {
   "messages": {
     "-KS3PV-iwUZp5wkNq70s": {
       "name": "person1",
       "text": "hey!"
     },
     "-KS3PXhIhs8J_inrExy4": {
       "name": "person2",
       "text": "what’s up?"
     }
   }
 },
 // トップレベルノード 2// 課金者のみにアクセス許可
 "special_chat": {
   "messages": {
     "-KR-DwqtKzlWGxSn9P0y": {
       "name": "person1",
       "text": "Want to go to the movies?"
     },
     "-KR4tIpWmNn-EYxquSrw": {
       "name": "person3",
       "text": "Yeah! Let’s meet at 7."
     }
   }
 },
// トップレベルノード 3// Uid ノード。課金booleanフラグあり
 "users": {
   "uid1": {
     "paid": true
   },
   "uid2": {
     "paid": false
   },
   "uid3": {
     "paid": true
   }
 }
}
-------------------------------------------------
 - DB セキュリティルール
{
 "rules": {
    // 認証者のみ読み書き
   "chat" : {
     "messages" : {
       ".read": "auth != null",
       ".write": "auth != null"
     }
   },
   // users DB の paid の値が true のユーザーのみ。
   "special_chat" : {
     "messages": {
       ".read":
       // root 変数を使用
       "root.child('users').child(auth.uid).child('paid').val() === true",
       ".write": 
       "root.child('users').child(auth.uid).child('paid').val() === true"
     }
   }
 }
}
-------------------------------------------------
その他の例

".write": "root.child('allow_writes').val() === true &&
          !data.parent().child('readOnly').exists() &&
          newData.child('foo').exists()"
-------------------------------------------------
{
 "rules": {
   "messages": {
     // 認証済みデータのみ読み書き許可
     ".read": "auth != null",
     ".write": "auth != null",
     "$id": {
        // 読み書き許可がカスケードされて。。。
        // メッセージがどんな 子 を持つべきかの検証ルールを定義
       ".validate": "newData.hasChildren(['name', 'text']) && !newData.hasChildren(['photoUrl']) || newData.hasChildren(['name', 'photoUrl']) && !newData.hasChildren(['text'])"
-------------------------------------------------




















▼ ルールをカスケードする
-------------------------------------------------
.read .write が true の場合 →そのルールが、子にもカスケードされる(子が false にしても効果なし)// 段々滝みたく連なっていく感じ。
親が true false の場合はカスケードされない。

-------------------------------------------------
例)
{
 "chat": {
   "messages": {// chat の子
     "-KRiMpW5bate5qV0Rt7i": {
       "name": "person1",
       "text": "hey!"
     },
     "-KQWHI_eepS4CGr8-kJd": {
       "name": "person2",
       "text": "what’s up?"
     }
   },
   "admin_blog": {// chat の子
     "Jan 1": "Welcome to my page",
     "Jan 2": "Enjoying the weather?"
   },
   "special_chat": {
   },
   "users": {
     "uid1": {
       "paid": true
     },
     "uid2": {
       "paid": false
     },
     "uid3": {
       "paid": true
     }
   }
 }
}
-------------------------------------------------
▼ 親のルールが強制され、子のルールは無視される
-------------------------------------------------
{
 "rules": {
   "chat" : {// "chat" の read/write が true になっている。
     // 子セクションにも true がカスケードされていく
     ".read": "true",
     ".write": "true",


     "admin_blog" : {
       // 親セクションで true になっているため、
       // ここで false としても、chat の子である限り、true がカスケードされ、false は適用されない。
       ".write" : "false"
     }
   }
 }
}
-------------------------------------------------
▼ 親がルールを定義していないため、子は独自ルールを立てられる
-------------------------------------------------
{
   "rules": {
      "chat" : {
         "messages" : {// "messages" の read/write が true になっている。
             ".read": "true",
             ".write": "true"
          },
         "admin_blog" : {
            // 親セクションにルールは定義されていないため、カスケードはおこらず、独自のルールを定義できる。
            ".read" : "true",
            ".write" : "false"
         }
      }
   }
}
-------------------------------------------------










$ Variables を使って、ルール定義時に 値を参照
-------------------------------------------------
{
  "rules": {
    "rooms": {
      // this rule applies to any child of /rooms/, the key for each room id
      // is stored inside $room_id variable for reference
      "$room_id": {
        "topic": {
          // the room's topic can be changed if the room id has "public" in it
          ".write": "$room_id.contains('public')"
        }
-------------------------------------------------
▼ 特殊変数 $other →それ以外 を意味する
-------------------------------------------------
{
  "rules": {
    "widget": {
      // a widget can have a title or color attribute
      "title": { ".validate": true },
      "color": { ".validate": true },

      // but no other child paths are allowed
      // in this case, $other means any key excluding "title" and "color"
      "$other": { ".validate": false }
    }
-------------------------------------------------










▼ 認証時に取得した値を使って ユーザーデータ保護
-------------------------------------------------
auth 変数// 認証時に下記情報が取得される
provider: password, facebook, github などなど
uid:
token:
-------------------------------------------------
{
  "rules": {
    "users": {
      "$user_id": {
        // grants write access to the owner of this user account
        // whose uid must exactly match the key ($user_id)
        ".write": "$user_id === auth.uid"
      }
    }
  }
}
-------------------------------------------------
子がすべてのユーザーの uid 値である単一の users ノードにすべてのユーザーを保存して、
ログインユーザーのみが自分のデータを参照できるようにする
-------------------------------------------------
{
  "rules": {
    "users": {
      "$uid": {
        ".read": "auth != null && auth.uid == $uid"
      }
    }
-------------------------------------------------
















▼ 複雑なルール              TODO:下記パートですべてのルールをまとめたい
-------------------------------------------------
{
  "rules": {
    "room_names": {
      // ログインしていれば、room_names は誰でも読める
      ".read": "auth !== null",

      "$room_id": {
        // this is just for documenting the structure of rooms, since
        // they are read-only and no write rule allows this to be set
        ".validate": "newData.isString()"
      }
    },

    "members": {
       // I can join or leave any room (otherwise it would be a boring demo)
       // I can have a different name in each room just for fun
       "$room_id": {
          // any member can read the list of member names
          ".read": "data.child(auth.uid).exists()",

          // room must already exist to add a member
          ".validate": "root.child('room_names/'+$room_id).exists()",

          "$user_id": {
             ".write": "auth.uid === $user_id",
             ".validate": "newData.isString() && newData.val().length > 0 && newData.val().length < 20"
          }
       }
    },

    "messages": {
      "$room_id": {
        // the list of messages for a room can be read by any member
        ".read": "root.child('members/'+$room_id+'/'+auth.uid).exists()",

        // room we want to write a message to must be valid
        ".validate": "root.child('room_names/'+$room_id).exists()",

        "$message_id": {
          // a new message can be created if it does not exist, but it
          // cannot be modified or deleted
          // any member of a room can write a new message
          ".write": "root.child('members/'+$room_id+'/'+auth.uid).exists() && !data.exists() && newData.exists()",

          // the room attribute must be a valid key in room_names/ (the room must exist)
          // the object to write must have a name, message, and timestamp
          ".validate": "newData.hasChildren(['user', 'message', 'timestamp'])",

          // the message must be written by logged in user
          "user": {
             ".validate": "newData.val() === auth.uid"
          },

          // the message must be longer than 0 chars and less than 50
          "message": { ".validate": "newData.isString() && newData.val().length > 0 && newData.val().length < 50" },

          // messages cannot be added in the past or the future
          // clients should use firebase.database.ServerValue.TIMESTAMP
          // to ensure accurate timestamps
          "timestamp": { ".validate": "newData.val() <= now" },

          // no other fields can be included in a message
          "$other": { ".validate": false }
        }
      }
    }
  }
}
-------------------------------------------------
















.validate
▼ プロパティ の型や長さなど制限を設ける// カスケードはされない
// .write ルールでアクセス権の付与に成功した後にのみ実行
-------------------------------------------------
{
  "rules": {
    "foo": {
      ".validate": "newData.isString() && newData.val().length < 100"
    }
  }
}
-------------------------------------------------
その他の例
"newData.isString() &&
              newData.val().matches(/^(19|20)[0-9][0-9][-\\/. ](0[1-9]|1[012])[-\\/. ](0[1-9]|[12][0-9]|3[01])$/)"

-------------------------------------------------
{
  "rules": {
    // write is allowed for all paths
    ".write": true,
    "widget": {
      // a valid widget must have attributes "color" and "size"
      // allows deleting widgets (since .validate is not applied to delete rules)
      ".validate": "newData.hasChildren(['color', 'size'])",
      "size": {
        // the value of "size" must be a number between 0 and 99
        ".validate": "newData.isNumber() &&
                      newData.val() >= 0 &&
                      newData.val() <= 99"
      },
      "color": {
        // the value of "color" must exist as a key in our mythical
        // /valid_colors/ index
        ".validate": "root.child('valid_colors/' + newData.val()).exists()"
      }
    }
  }
}
-------------------------------------------------
// Java における書き込み 成否例
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("widget");

// PERMISSION_DENIED: does not have children color and size
ref.setValue("foo");

// PERMISSION DENIED: does not have child color
ref.child("size").setValue(22);

// PERMISSION_DENIED: size is not a number
Map<String,Object> map = new HashMap<String, Object>();
map.put("size","foo");
map.put("color","red");
ref.setValue(map);

// SUCCESS (assuming 'blue' appears in our colors list)
map = new HashMap<String, Object>();
map.put("size", 21);
map.put("color","blue");
ref.setValue(map);

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child("size").setValue(99);
-------------------------------------------------




例その2
-------------------------------------------------
{
  "rules": {
    // this variant will NOT allow deleting records (since .write would be disallowed)
    "widget": {
      // a widget must have 'color' and 'size' in order to be written to this path
      ".write": "newData.hasChildren(['color', 'size'])",
      "size": {
        // the value of "size" must be a number between 0 and 99, ONLY IF WE WRITE DIRECTLY TO SIZE
        ".write": "newData.isNumber() && newData.val() >= 0 && newData.val() <= 99"
      },
      "color": {
        // the value of "color" must exist as a key in our mythical valid_colors/ index
        // BUT ONLY IF WE WRITE DIRECTLY TO COLOR
        ".write": "root.child('valid_colors/'+newData.val()).exists()"
      }
    }
  }
}
-------------------------------------------------
Firebase ref = new Firebase(URL + "/widget");

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
Map<String,Object> map = new HashMap<String, Object>();
map.put("size", 99999);
map.put("color", "red");
ref.setValue(map);

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.child("size").setValue(99);
-------------------------------------------------


















▼ セキュリティ
▼ A JSON Web Token
-------------------------------------------------
DB と クライアント間 などで、JSON OBJ として情報をやり取りする際の標準セキュア方法。
-------------------------------------------------
プロバイダ:   メール+パスワード、Googleサインイン、Facebookログイン
Uid    :   プロバイダ横断してユニークであることが保証されるID。
-------------------------------------------------

▼ Firebase Auth ID
-------------------------------------------------
a web token inside of the Auth web token! 
下記データを含んでいる
email
email_verified →boolean。 メアドが verify されてたら true。
name
sub →Firebase Uid
firebase.identities →対象ユーザーアカウントに紐付いたすべての ID の辞書
firebase.sign_in_provider →対象の Firebase Auth ID token を取得するのに使った サインインプロバイダ
-------------------------------------------------
















▼ データのインデックス作成
-------------------------------------------------
開発時にはインデックス作成不要。
配信開始直前でよい。// 配信してから、規模は拡大するから

-------------------------------------------------
アプリの規模が拡大するにつれ、このクエリのパフォーマンスは低下する。
対策:
クエリ実行対象のキーを定義しておけば、サーバにそのキーのインデックスが作成され、パフォーマンスが向上する。
// ノードのキー インデックスは自動的に作成されるため、明示的にインデックスを作成する必要はありません。
-------------------------------------------------






▼ orderByChild を使用したインデックス作成
-------------------------------------------------
 - 恐竜DB があるとする
{
  "lambeosaurus": {
    "height" : 2.1,
    "length" : 12.5,
    "weight": 5000
  },
  "stegosaurus": {
    "height" : 4,
    "length" : 9,
    "weight" : 2500
  }
}
-------------------------------------------------
{
  "rules": {
    "dinosaurs": {
      ".indexOn": ["height", "length"]// 左記 の値によるソートクエリが早くなる
    }
  }
}
-------------------------------------------------


▼ orderByValue によるインデックス作成
-------------------------------------------------
// 恐竜の戦闘力で構成されるリーダーボードを作成するとする
{
  "scores": {
    "bruhathkayosaurus" : 55,
    "lambeosaurus" : 21,
    "linhenykus" : 80,
    "pterodactyl" : 93,
    "stegosaurus" : 5,
    "triceratops" : 22
  }
}
-------------------------------------------------
// /scores ノードに .value ルールを追加すると、クエリを最適化できる
{
  "rules": {
    "scores": {
      ".indexOn": ".value"
    }
  }
}
-------------------------------------------------




▼ オフラインでの永続性有効化
-------------------------------------------------
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
-------------------------------------------------