Google Cloud DataStoreをHeroku上のRailsアプリから使う

## なぜやるか

GCPは無料枠が大きく、小規模のサービスであればDataStoreは無料枠で使い続けられると思います。（ http://my-android-server.appspot.com/ は、実際に無料枠で収まっています。）

Google App Engineも小規模であれば無料で使い続けられますが、利用できる言語に制限があります。

Rubyをやりたくなったので、Herokuへの移行を検討していますが、DataStoreはそのまま使い続けたかったので、DataStoreを外部から利用する方法を試しました。

## GCP上でサービスアカウントを作成

https://console.cloud.google.com/iam-admin/serviceaccounts/project?project=my-android-server
※`my-android-server`の部分は、プロジェクトに合わせて変える。

![image](https://qiita-image-store.s3.amazonaws.com/0/8708/e25632ef-9ce7-b7a3-6bca-4eae032fd7cf.png)


「サービスアカウントを作成」をクリック

![image](https://qiita-image-store.s3.amazonaws.com/0/8708/8e36bf71-1c4e-334e-a5ec-311438ee8833.png)

「サービスアカウント名」を適当に入力、
「役割」にて必要な権限（今回であれば「編集者」と「データストアオーナー」）を選択し、
「新しい秘密鍵の提供」にチェックを入れ、「作成」をクリックします。

※「データストアオーナー」だけだと、権限のエラーが出ました。。。けど、今「編集者」を外しても動作する。謎。

JSONファイルがダウンロードされるので、Railsアプリケーションの任意の場所に保管します。
今回は、直下にcertディレクトリを作成し、そこに配置しました。
（ファイル名は`my-android-server-000000000000.json`として進めます。）

## Herokuで使うための準備

用意したcertディレクトリをGitにコミットしたくなかったので、Herokuの環境変数として登録することにしました。

`heroku config:add GCP_KEY="$(cat cert/my-android-server-000000000000.json)"`

これで、jsonの内容を環境変数から取得できるようになります。

## Railsアプリケーションの実装

https://developers.google.com/api-client-library/ruby/auth/service-accounts
https://cloud.google.com/datastore/reference/rest/v1beta3/projects/lookup
を参考に実装していきます。

とりあえず、controllerにべた書きしました。（ホントはlibディレクトリの中とかで抽象化したほうが良いのかな？）

```ruby
require 'googleauth'
require 'google/apis/datastore_v1'

class WelcomeController < ApplicationController
  # https://github.com/google/google-api-ruby-client/blob/master/generated/google/apis/datastore_v1/classes.rb
  ServiceAccountCredentials  = Google::Auth::ServiceAccountCredentials
  Datastore  = Google::Apis::DatastoreV1
  RunQueryRequest = Google::Apis::DatastoreV1::RunQueryRequest
  GqlQuery = Google::Apis::DatastoreV1::GqlQuery
  GqlQueryParameter = Google::Apis::DatastoreV1::GqlQueryParameter
  Value = Google::Apis::DatastoreV1::Value

  def index
    datastore = Google::Apis::DatastoreV1::DatastoreService.new

    dummyFile = DummyFile.new(ENV['GCP_KEY']) if ENV['GCP_KEY'].present?
    datastore.authorization = ServiceAccountCredentials.make_creds(
        json_key_io: dummyFile || File.open('cert/my-android-server-000000000000.json'),
        scope: [
          Datastore::AUTH_CLOUD_PLATFORM,
          Datastore::AUTH_DATASTORE
        ]
    )

    query = GqlQuery.new(
        query_string: 'SELECT * FROM GcmModel LIMIT @limit',
        named_bindings: {
          limit: GqlQueryParameter.new(value: Value.new(integer_value: '5'))
        }
    )
    request = RunQueryRequest.new(gql_query: query)
    result = datastore.run_project_query('my-android-server', request)
    render json: result.batch.entity_results[0].entity.as_json
  end

  class DummyFile
    attr_accessor :read
    def initialize(read)
       @read = read
    end
  end
end
```

`my-android-server`というプロジェクトの、GcmModelを5件取得して、1件目をjson形式で返しています。

`DummyFile`あたりが上手く実装できなくて、`GCP_KEY`が存在する場合はそこから情報を読み取り、それ以外（ローカル）ではファイルから読み取るようにしています。
内部的に`read`を実行していたので、`read`というプロパティを持たせることで回避しました。

## 結果

下記のようなjsonを取得できます。

```
{
  "properties": {
    "registrationId": {
      "string_value": "XXXX"
    },
    "registDate": {
      "timestamp_value": "2012-10-08T21:59:59.661960Z"
    }
  },
  "key": {
    "partition_id": {
      "project_id": "my-android-server"
    },
    "path": [
      {
        "kind": "GcmModel",
        "id": "111111"
      }
    ]
  }
}
```

GcmModelとしては、`registrationId`と`registDate`があります。
`XXX_value`というのがあるのが、ちょっと面倒ですね。