google-api-ruby-clientでBigQuery使い方メモ


rubyでgoogleのAPIを使うなら[google-api-ruby-client](https://github.com/google/google-api-ruby-client)や他のラッパーしたgemを使用することがあると思いますが、[google-api-ruby-client](https://github.com/google/google-api-ruby-client) でBigQueryを扱うのに結構苦労したので忘れない為のメモになります。

今回のメモはbatchでbigqueryを叩く前提になります。
なのでフロント側でユーザー認証させたい場合は認証の仕方が変わりますのでご注意ください。

先に謝っておきます。当方rubyはまだ初心者なのでツッコミ歓迎です。




## 環境

* centos6.7


## 事前準備に必要なもの

* Ruby 2.3.1p112
* Rails 4.2.6
	* 今回は特にrailsなんちゃらはでてきません
* [Google Cloud Platform](https://cloud.google.com/)で課金設定まで済ましていること
	* [はじめの一歩が踏み出せない人のためのBigQuery入門](http://www.apps-gcp.com/bigquery-introduction/)を参考にしてデータセットの作成まで完了しておいてください。
* 認証情報でサービス アカウント クライアントを作成して認証情報のjsonを作成していること

認証情報のjsonは下記のようなフォーマットのjsonになります。

```
{
  "type": "service_account",
  "project_id": "xxxxxxxxxx",
  "private_key_id": "xxxxxxxxxxxxx",
  "private_key": "-----BEGIN PRIVATE KEY-----xxxxxxxxxxxxxxxxxxx\n-----END PRIVATE KEY-----\n",
  "client_email": "123456789-xxxxxx@developer.gserviceaccount.com",
  "client_id": "123456789-xxxxxxxxxx.apps.googleusercontent.com",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://accounts.google.com/o/oauth2/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/123456789-xxxxxxxxx%40developer.gserviceaccount.com"
}
```


## 参考にしたサイト

* [Google Cloud Platform API Reference BigQuery](https://cloud.google.com/bigquery/docs/reference/v2/)
* [RubyでBigQuery始めました](http://tech.feedforce.jp/bigquery_tips.html)
* [bq コマンドライン ツール クイックスタート](https://cloud.google.com/bigquery/bq-command-line-tool-quickstart?hl=ja)
* [bigquery-client という gem を作った](http://qiita.com/ttanimichi/items/4a70ae2610ca43251bc7)
* [abronte/BigQuery](https://github.com/abronte/BigQuery)


## 最近google-api-ruby-clientが大幅なアップデートがされています

すみません。最近っていつなんだよ？ってところまでは調べていません。

参考: [google-api-ruby-clientが非互換なアップデートされた
](http://rcmdnk.github.io/blog/2016/01/17/blog-octopress-javascript-analytics/)

なのでbigquery ruby とかでググって試してみても、メソッドのインターフェースが大分違っているので注意しましょう。

この関係で[abronte/BigQuery](https://github.com/abronte/BigQuery)などはgemのバージョン指定しないと動かないので注意が必要です。

```
gem 'google-api-client', '~> 0.8.6'
```

[abronte/BigQuery](https://github.com/abronte/BigQuery)を使う手もあったですが、最近Google関連のものが頻繁にアップデートされている印象を受けたので
本家のgoogle-api-clientを使うことにしました。

ちゃんと試していないですが、BigQueryをrubyで使いたいという要件ならおそらく
* [abronte/BigQuery](https://github.com/abronte/BigQuery)
* [ttanimichi/bigquery-client](https://github.com/ttanimichi/bigquery-client)

を使ったほうが楽に扱えると思います。


## exampleとかググってもアップデート後の使い方はほとんどありません


なのでruby初心者だけども、Gemの中身見まくって頑張りました。

## BigQuery UIで動作確認をするときの注意事項

* データセットの作成
* データセットにテーブルを作成する

まではUIで確認できますが、テーブルにデータ投入後のデータの確認は人によって見れない場合があります。
僕はコレでAPIレスポンスが200なのにBigQuery UI でデータが入っていない現象(実際はbqで見れば入っていた)で半日ハマりました。。

データ投入の確認は [BigQuery CommandLine Tool](https://cloud.google.com/bigquery/bq-command-line-tool#installation)を使って確認することを強くおすすめします。


```shell:/path/to/hoge
bq query "SELECT count(*) FROM dataset_id.table_id"
```

## google-api-clientの構成と仕組み

ちゃんと調べていないのですが、google-api-clientのリポジトリを調べてもservice単位でのソースコードは検索できません。
なぜならばサービス単位のAPIはgenerateされて作られてるみたいです。
通常のGemの構成ならば ``` lib/ ``` 以下にファイルがあるはずですが、google-api-clientの場合は ``` generated/ ``` 以下にAPIサービス関連のロジックが配置されています。

``` lib/ ``` 以下は認証系のメソッドとAPIコール系のメソッドとかしかない感じです(厳密には調べていません)


以下tree

```shell:/path/to/rails_root
vendor/bundle/ruby/2.3.0/gems/google-api-client-0.9.6
├── bin
├── generated
│   └── google
│       └── apis #一部APIは省略
│           ├── analytics_v3
│           ├── bigquery_v2
│           ├── youtube_partner_v1
│           ├── youtube_v3
│           └── youtubereporting_v1
├── lib
│   └── google
│       ├── api_client
│       │   └── auth
│       │       └── storages
│       └── apis
│           ├── core
│           └── generator
│               └── templates
├── rakelib
├── samples
│   ├── cli
│   │   └── lib
│   │       └── samples
│   └── web
│       └── views
└── third_party
3 [error opening dir]
```

### google-api-client-0.9.6/generated/google/apis/bigquery_v2.rb

Google APIのscopeの定義が記載されています。

### google-api-client-0.9.6/generated/google/apis/bigquery_v2/service.rb

API実行のインターフェースメソッドがあります。

今回はservice.rbの中のメソッド

* get_table
	* BigQueryデータセットの中にあるテーブル情報を取得
* insert_table
	* BigQueryデータセットにテーブルを作成する
* delete_table
	* BigQueryデータセットの中にあるテーブルを削除する
* insert_all_table_data
	* BigQueryデータセットの中にあるテーブルにstreamingデータ挿入をする

の使い方のメモです
(select系は追って記載します)


## 何はともあれGemをインストールする

Gemfileに記載しましょ

```ruby:Gemfile
gem 'google-api-client'
```

bundle install

```shell:/path/to/rails_root
bundle install
```


## 認証をする

batch実行([rails runner](http://qiita.com/3yatsu/items/416411c0a8f696dbf99e))を想定した認証になります。
scopeは状況に応じて適切な設定を行ってください。

```ruby:/path/to/hoge/rails_root/lib/tasks/hoge.rb
require 'googleauth'
require 'googleauth/stores/file_token_store'
require 'google/apis/bigquery_v2'

module Tasks
	module Hoge
      GOOGLE_CRED_JSON_PATH      = File.join(File.expand_path(File.dirname(__FILE__)), "../../config/tasks/google-auth-cred.json")
	  ServiceAccountCredentials  = Google::Auth::ServiceAccountCredentials
      BigQuery                   = Google::Apis::BigqueryV2

	  def self.init
	    @bigquery = BigQuery::BigqueryService.new
        @bigquery.authorization = ServiceAccountCredentials.make_creds(
              json_key_io: File.open(GOOGLE_CRED_JSON_PATH),
              scope: [
                self::BigQuery::AUTH_BIGQUERY,
                self::BigQuery::AUTH_CLOUD_PLATFORM,
                self::BigQuery::AUTH_DEVSTORAGE_FULL_CONTROL,
                self::BigQuery::AUTH_BIGQUERY_INSERTDATA
              ]
         )

         @bigquery
	  end

	end
end
```

## get_table

BigQueryデータセットの中にあるテーブル情報を取得します。
動作確認をするために最初に何かしらのサンプルテーブルを作成しておいてください。

先ほどの認証部分を除外した内容です。
table_idはtable名を指定してください。

```ruby:/path/to/hoge/rails_root/lib/tasks/hoge.rb
module Tasks
  module Hoge
    PROJECT_ID                 = "sample-bigquery"
    DATASET_ID                 = "sample-dataset"

    def self.execute
      # データテーブル情報を取得する
      @bigquery.get_table(self::PROJECT_ID, self::DATASET_ID, "teble_id")
    end
  end
end
```


## insert_table

BigQueryのデータセットにテーブルを作成する
schemaの定義一覧などはBigQueryUI等をみれば、どういう定義があるかわかると思います。




```ruby:/path/to/hoge/rails_root/lib/tasks/hoge.rb
module Tasks
  module Hoge
    PROJECT_ID                 = "sample-bigquery"
    DATASET_ID                 = "sample-dataset"

    def self.execute
      schema = [
        { name: "id", type: "INTEGER", mode: 'required', description: "uniq id"},
        { name: "path", type: "STRING", description: "testing string filed"},
        { name: "pv", type: "INTEGER", description: "testing string filed"}
      ]

    　table = BigQuery::Table.new(
      table_reference: { project_id: self::PROJECT_ID, dataset_id: self::DATASET_ID, table_id: "table_id" },
      schema: { fields: schema }
      )
      begin
        @bigquery.insert_table(
          self::PROJECT_ID,
          self::DATASET_ID,
          table
      )
      rescue Google::Apis::ClientError => ex
        p ex.message
      end

    end
  end
end
```

## delete_table

BigQueryデータセットの中にあるテーブルを削除する

```ruby:/path/to/hoge/rails_root/lib/tasks/hoge.rb
module Tasks
  module Hoge
    PROJECT_ID                 = "sample-bigquery"
    DATASET_ID                 = "sample-dataset"

    def self.execute
      begin
        @bigquery.delete_table(
          self::PROJECT_ID,
          self::DATASET_ID,
          "table_id"
        )
      rescue Google::Apis::ClientError => ex
        p ex.message
      end

    end
  end
end
```


## insert_all_table_data



BigQueryデータセットの中にあるテーブルにstreamingでデータ挿入をします。
streamingデータの場合は容量単位で課金されますので、ご注意ください。

[BigQuery 料金形態](https://cloud.google.com/bigquery/pricing?hl=ja)


```ruby:/path/to/hoge/rails_root/lib/tasks/hoge.rb
module Tasks
  module Hoge
    PROJECT_ID                 = "sample-bigquery"
    DATASET_ID                 = "sample-dataset"

    def self.execute
      rows = [
        {
          :insertId => "hoge",
          :json => { id: 1, path: "12345", pv: 99999 }
        },
        {
          :insertId => "fuga",
          :json => { id: 2, path: "67890", pv: 99999 }
        },
      ]
      insert_all_table_data_request = Google::Apis::BigqueryV2::InsertAllTableDataRequest.new({
        :rows => rows
      })
      begin
        @bigquery.insert_all_table_data(
          self::PROJECT_ID,
          self::DATASET_ID,
          "table_id",
          insert_all_table_data_request
        )
      rescue Google::Apis::ClientError => ex
        p ex.message
      end

    end
  end
end
```

## 認証までまとめるとこんな感じ

```ruby:/path/to/hoge/rails_root/lib/tasks/hoge.rb
module Tasks
  module Hoge
    GOOGLE_CRED_JSON_PATH      = File.join(File.expand_path(File.dirname(__FILE__)), "../../config/tasks/google-auth-cred.json")
	ServiceAccountCredentials  = Google::Auth::ServiceAccountCredentials
    BigQuery                   = Google::Apis::BigqueryV2

	def self.init
	  @bigquery = BigQuery::BigqueryService.new
      @bigquery.authorization = ServiceAccountCredentials.make_creds(
        json_key_io: File.open(GOOGLE_CRED_JSON_PATH),
        scope: [
          self::BigQuery::AUTH_BIGQUERY,
          self::BigQuery::AUTH_CLOUD_PLATFORM,
          self::BigQuery::AUTH_DEVSTORAGE_FULL_CONTROL,
          self::BigQuery::AUTH_BIGQUERY_INSERTDATA
        ]
      )

      @bigquery
	end

	#table情報を参照
    def self.get_table
      @bigquery.get_table(self::PROJECT_ID, self::DATASET_ID, "teble_id")
    end


	#テーブルの作成
    def self.create_table
      schema = [
        { name: "id", type: "INTEGER", mode: 'required', description: "uniq id"},
        { name: "path", type: "STRING", description: "testing string filed"},
        { name: "pv", type: "INTEGER", description: "testing string filed"}
      ]

    　table = BigQuery::Table.new(
      table_reference: { project_id: self::PROJECT_ID, dataset_id: self::DATASET_ID, table_id: "table_id" },
      schema: { fields: schema }
      )
      begin
        @bigquery.insert_table(
          self::PROJECT_ID,
          self::DATASET_ID,
          table
      )
      rescue Google::Apis::ClientError => ex
        p ex.message
      end

    end


	# テーブルの削除
    def self.delete_table
      begin
        @bigquery.delete_table(
          self::PROJECT_ID,
          self::DATASET_ID,
          "table_id"
        )
      rescue Google::Apis::ClientError => ex
        p ex.message
      end

    end


	# streaming insert
    def self.insert_all_table_data
      rows = [
        {
          :insertId => "hoge",
          :json => { id: 1, path: "12345", pv: 99999 }
        },
        {
          :insertId => "fuga",
          :json => { id: 2, path: "67890", pv: 99999 }
        },
      ]
      insert_all_table_data_request = Google::Apis::BigqueryV2::InsertAllTableDataRequest.new({
        :rows => rows
      })
      begin
        @bigquery.insert_all_table_data(
          self::PROJECT_ID,
          self::DATASET_ID,
          "table_id",
          insert_all_table_data_request
        )
      rescue Google::Apis::ClientError => ex
        p ex.message
      end

  end
end

```


## 最後に

結局シンプルなのですが、ここまでたどり着くまでに苦労した・・・・・
なruby初心者のつぶやきです・・・・