RailsでGoogle Spreadsheetsをいい感じに使える＋ActiveRecordとデータ同期とか


https://github.com/tkawa/activeresource-google_spreadsheets

**(追記) OAuth2認証に対応しました。**

## こういうの作ってます。

こういうスプレッドシートを用意しておく。

タイトルは何でもいい。idは必須。シートの名前は標準では`[モデル名]_rows`。

![screenshot](https://f.cloud.github.com/assets/562433/1819790/46fcdde0-70b2-11e3-97a5-54ffce3ac824.png)

https://docs.google.com/spreadsheet/ccc?key=0AtkgjSZyl3NLdERqdkdMZk01alFLWXZUZUtuNlcwN3c

```ruby
spreadsheet = GoogleSpreadsheets::Enhanced::Spreadsheet.find('0AtkgjSZyl3NLdERqdkdMZk01alFLWXZUZUtuNlcwN3c')
# GET /feeds/spreadsheets/private/full/0AtkgjSZyl3NLdERqdkdMZk01alFLWXZUZUtuNlcwN3c HTTP/1.1
# Authorization: GoogleLogin auth=xxxx
# Accept: application/atom+xml
# Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
# User-Agent: Ruby
# Connection: close
# Host: spreadsheets.google.com
# 
# HTTP/1.1 200 OK
# …
#
# => #<GoogleSpreadsheets::Enhanced::Spreadsheet:0x007f95d2283880>

worksheet = spreadsheet.worksheets.find_by(title: 'user_rows')
# GET /feeds/worksheets/0AtkgjSZyl3NLdERqdkdMZk01alFLWXZUZUtuNlcwN3c/private/full HTTP/1.1
# …
#
# => #<GoogleSpreadsheets::Enhanced::Worksheet:0x007f95d2338780>

rows = worksheet.rows
# GET /feeds/list/0AtkgjSZyl3NLdERqdkdMZk01alFLWXZUZUtuNlcwN3c/od1/private/full HTTP/1.1
# …
#
# => #<GoogleSpreadsheets::Enhanced::Collection:0x007f95d2492158>
```

こんな感じで、spreadsheetからたどっていける。



## ActiveRecordとの同期

```app/models/user.rb
class User < ActiveRecord::Base
  include GoogleSpreadsheets::Enhanced::Syncing
  sync_with :user_rows, spreadsheet_id: '0AtkgjSZyl3NLdERqdkdMZk01alFLWXZUZUtuNlcwN3c'
  after_commit :sync_user_row
end
```

`sync_with :user_rows`によって、以下のメソッドが定義される。（`has_many :user_rows`みたいなイメージ）

* `user_rows`
* `sync_with_user_rows`
* `sync_user_row`


### `User.user_rows`
このメソッドは自動でspreadsheetsからリンクをたどって、ActiveResourceオブジェクト（のコレクション）を返す。

```ruby
user_rows = User.user_rows

# GET /feeds/spreadsheets/private/full/0AtkgjSZyl3NLdERqdkdMZk01alFLWXZUZUtuNlcwN3c HTTP/1.1
# …
# GET /feeds/worksheets/0AtkgjSZyl3NLdERqdkdMZk01alFLWXZUZUtuNlcwN3c/private/full HTTP/1.1
# …
# GET /feeds/list/0AtkgjSZyl3NLdERqdkdMZk01alFLWXZUZUtuNlcwN3c/od1/private/full HTTP/1.1
# …
# => #<GoogleSpreadsheets::Enhanced::Collection:0x007f87cc7bccc0 @elements=[#<GoogleSpreadsheets::Enhanced::Row:0x007f87cc7bcb30 @attributes={…}, …

```

ActiveResourceオブジェクトなので`find`とか`save`とか、ActiveRecordっぽい扱いが可能。

```ruby
user_row = user_rows.find(id)
user_row.name = 'foo'
user_row.save
# PUT /feeds/list/0AtkgjSZyl3NLdERqdkdMZk01alFLWXZUZUtuNlcwN3c/od1/private/full HTTP/1.1
# …
```

### `User.sync_with_user_rows`

Spreadsheetのデータは`sync_with_user_rows`メソッドでActiveRecord側に一括同期可能。

```ruby
User.sync_with_user_rows

# GET /feeds/list/0AtkgjSZyl3NLdEE1S0lvbEdZLU00ZndhMXFzLW44SlE/od1/private/full HTTP/1.1
#
# begin transaction
# UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = 1  [["name", “foo"],  ["updated_at", Sun, 29 Dec 2013 23:17:30 JST +09:00]]
# UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = 13  [["name", "bar"],  ["updated_at", Sun, 29 Dec 2013 23:17:31 JST +09:00]]
# commit transaction

```

### `User#sync_user_row`

レコード単位でシート側へ同期するメソッド。`after_commit :sync_user_row`を書いておけば、`user_row`ではなく普通の`user`オブジェクトをsaveしても同期されるようになる。

```ruby
user = User.find(id)
user.name = 'bar'
user.save

# UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = 13  [["name", "bar"],  ["updated_at", Sun, 29 Dec 2013 23:17:30 JST +09:00]]
#
# PUT /feeds/list/0AtkgjSZyl3NLdERqdkdMZk01alFLWXZUZUtuNlcwN3c/od1/private/full HTTP/1.1
# …
```


## 設定

0.1.0からOAuth2に対応しました。
`GoogleSpreadsheets::Base.auth_type = :bearer`として、`GoogleSpreadsheets::Base.access_token`に取得したaccess tokenをセットしてください。
`GoogleSpreadsheets::Base.access_token do ... end`の形でブロックも渡せます。必要なときに呼び出されるので、refreshなどに使えます。

### Service Accounts の場合

ユーザ個別の権限が必要ない(2-legged)場合、[Service Accounts](https://developers.google.com/accounts/docs/OAuth2ServiceAccount)という方式でaccess tokenを取得します。この場合に便利なクラスを用意しました。

[Google Developers Console](https://console.developers.google.com/project)からプロジェクトを作成し、「認証情報」から「新しいクライアントIDを作成」、「サービスアカウント」を選んで作成してください。

![Google_Developers_Console.jpg](https://qiita-image-store.s3.amazonaws.com/0/6740/2cc8a606-2023-bee6-59bc-7e20be2d7015.jpeg "Google_Developers_Console.jpg")

クライアントIDが作成できたら、「新しいJSONキーを生成」でJSONファイルをダウンロードします。
その中の client_email と private_key を使用して以下のように設定してください。


```config/initializers/google_spreadsheet.rb
GoogleSpreadsheets::Base.auth_type = :bearer
GoogleSpreadsheets::Base.access_token =
  GoogleSpreadsheets::Auth::ServiceAccountsAccessToken.new(
    client_email: '000000000000-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@developer.gserviceaccount.com',
    private_key_pem: "-----BEGIN PRIVATE KEY-----\n..."
  )
```

スプレッドシートが公開でない場合は、上の`client_email`に設定した`...@developer.gserviceaccount.com`のユーザに共有設定で権限を与えることでアクセスできるようになります。

参考：http://d.hatena.ne.jp/sugyan/20130112/1357996092

他の方式に対応したクラスも作るかも。

### 以下のClientLogin認証は古い記述で非推奨です

Googleユーザ名とパスワードの設定が必要。

```config/initializers/google_spreadsheet.rb
GoogleSpreadsheets::Base.user = 'google_acount@gmail.com'
GoogleSpreadsheets::Base.password = 'password'
```

※2段階認証を有効にしている場合は、[アプリケーション固有のパスワードを生成](https://support.google.com/accounts/answer/185833?hl=ja)してそれを使ってください。

### カスタムクラスを使う場合

`sync_with :user_rows`の場合、名前に対応する`UserRow`クラスがあればそれが使われる。（`has_many`みたいなイメージ）

スプレッドシート上の列名が属性名と異なる場合、カスタムクラスを作成して`attr_aliases`で指定できる。

```app/models/user_row.rb
class UserRow < GoogleSpreadsheets::Enhanced::Row
  attr_aliases(
    name:    '名前',
    address: '住所'
  )
  # user.name で 「名前」列
  # user.address で 「住所」列にアクセスできる
end
```

スプレッドシートの列名は、アルファベット大文字や記号類が使用できない（すべて小文字になり、記号類は削除される）ので注意。