概要
スプレッドシートからデータを取得してjsonに整形するみたいなAPIを作成してみました。
意外とサービスアカウントを使っている記事が少なかったので少しでも参考になればと思います。
環境
Ruby: 2.5.7
Ruby on Jets: 2.3.18
(Railsライクなフレームワークなので、Railsでもできます)
サービスアカウントについて
ユーザではなくアプリケーションレベルで使用するアカウント。ユーザで認証する場合はそのユーザがアプリケーションの管理からはずれた場合などに認証情報を変えなければならなくなるため不便。
サービスアカウントはユーザに依存しないのでユーザなど関係なくアプリケーションからAPIを叩く場合にはこちらが便利。
ちなみに今回はバックエンドからユーザなど関係なくAPIを叩くために使うのでサービスアカウントを利用しました。
サービスアカウントの作成
作成していない場合は以下を参考に作成してください。ここでは省きます
https://support.google.com/a/answer/7378726?hl=ja
サービスアカウントからcredentialファイルをjsonファイルで取得
認証情報 > サービスアカウントを管理 > 作成したアカウントの操作 > 鍵を作成
json形式で鍵を作成して、
ダウンロードしたらアプリのトップディレクトリにgoogle-sa-credential.json
という名前で保存
APIを有効にする
https://console.developers.google.com/apis/dashboard
GCPコンソールの「APIとサービス」から
- Google Drive API
- Google Sheets API
を検索してそれぞれ有効にする。
サービスアカウントでフォルダ、スプレッドシートを作成する
サービスアカウントでスプレッドシートを操作する際には、以下のいずれかの必要がある。
- サービスアカウントによって作成されている
- サービスアカウントが編集者として共有されている
- 公開されている
スプレッドシートの「共有」でサービスアカウントを許可できるならば、共有した時点でこのタスクを必要としないので飛ばしてもOKです。
以下に2つのrakeタスクを記述しました
- 指定したフォルダID配下にフォルダを作成する(指定しなければ最上階にフォルダを作成する)
- 指定したフォルダID配下にスプレッドシートを作成する
# lib/tasks/google_drive.rake
namespace :google_drive do
desc 'Create folder with Service account'
task :create_folder, [:title, :email_address, :collection_id] => :environment do |_, args|
session = create_session
folder = if args[:collection_id]
session.file_by_id(args[:collection_id]).create_subcollection(args[:title])
else
session.root_collection.create_subcollection(args[:title])
end
folder.acl.push(type: 'user', email_address: args[:email_address], role: 'writer')
p "Created folder: #{folder.human_url}"
end
desc 'Create sheet with Service account'
task :create_sheet, [:title, :email_address, :collection_id] => :environment do |_, args|
session = create_session
sheet = session.file_by_id(args[:collection_id]).create_spreadsheet(args[:title])
sheet.acl.push(type: 'user', email_address: args[:email_address], role: 'writer')
p "Created sheet: #{sheet.human_url}"
end
def create_session
::GoogleDrive::Session.from_service_account_key('google-sa-credential.json')
end
end
$ bundle exec rake 'google_drive:create_folder[<フォルダタイトル>,<共有したいユーザのメールアドレス>]'
$ bundle exec rake 'google_drive:create_sheet[<シートタイトル>,<共有したいユーザのメールアドレス>]'
補足
collection: folderの意味
ACL: アクセス制御リスト。IAMと似て非なるもの。バケットやオブジェクトに柔軟に権限を与えるっぽい。
その後の実装方針
- gem google_driveを使用する
- spreadsheet → worksheet → json
- gem google_api_clientを使用する
- 柔軟性がない(jsonで出力できてもどちらにせよしたい形に変換しなければならない)
json構造を柔軟に変更できるように1の方針で実装しました。
所々moduleに切り分けていますが必須ではないです
また、routingは省略します
Gemfile
gem 'google_drive'
controller
# controller
session = GoogleDriveSession.create_session
service = SpreadSheetToHashService.new(session)
service.run!
render json: JSON.dump(service.records)
session作成module
# google_drive_session.rb
module GoogleDriveSession
CREDENTIAL_PATH = 'google-sa-credential.json'
def self.create_session
return ::GoogleDrive::Session.from_service_account_key(CREDENTIAL_PATH)
end
end
ちなみに自分の場合は開発環境以外ではSecrets Managerから取得する(gem 'aws-sdk-secretsmanager'を使用)
# google_drive_session.rb
module GoogleDriveSession
CREDENTIAL_PATH = 'google-sa-credential.json'
def self.create_session
return ::GoogleDrive::Session.from_service_account_key(CREDENTIAL_PATH) if Jets.env.development?
credential_json = RequestSecretsManager.request('/<project_name>/google-sa-credential')
credential_hash = JSON.parse(credential_json)
File.open("/tmp/#{CREDENTIAL_PATH}", 'w') do |f|
JSON.dump(credential_hash, f)
end
::GoogleDrive::Session.from_service_account_key("/tmp/#{CREDENTIAL_PATH}")
end
end
# request_secrets_manager.rb
require 'aws-sdk-secretsmanager'
module RequestSecretsManager
def self.request(secret_name)
client = Aws::SecretsManager::Client.new(region: Jets.aws.region)
get_secret_value_response = client.get_secret_value(secret_id: secret_name)
get_secret_value_response.secret_string
end
end
service
# spread_sheet_to_hash_service.rb
class SpreadSheetToHashService
attr_reader :records
SPREADSHEET_ID = '<スプレッドシートのID>'
WORKSHEET_ID = '<ワークシートのID(1ページ目は0)>'
HEADER_COUNT = 1
def initialize(session)
@session = session
end
def run!
worksheet = SpreadSheet.identify_worksheet_by_id(@session, SPREADSHEET_ID, WORKSHEET_ID)
convert_worksheet_to_hash(worksheet)
end
private
def convert_worksheet_to_hash(worksheet)
# ヘッダをスキップする
@records = worksheet.rows(HEADER_COUNT).map { |row|
# ここでセル単位で好きな形にする
# rowは行のこと、一行ずつのイテレータ
# row[0]: 1列目の値
# row[1]: 2列目の値
}
end
end
spread sheet操作用module
# spread_sheet.rb
module SpreadSheet
def self.identify_worksheet_by_id(session, spread_sheet_id, work_sheet_id)
spreadsheet = session.file_by_id(spread_sheet_id)
spreadsheet.worksheet_by_sheet_id(work_sheet_id)
end
end
まとめ
これでスプレッドシートの内容を柔軟にjsonへ変更してレスポンスとして返すことができます!