LoginSignup
1
0

More than 3 years have passed since last update.

Rubyでスプレッドシートからデータを取得するAPIを作成してみた(with サービスアカウント)

Posted at

概要

スプレッドシートからデータを取得して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ファイルで取得

認証情報 > サービスアカウントを管理 > 作成したアカウントの操作 > 鍵を作成

image.png

json形式で鍵を作成して、
ダウンロードしたらアプリのトップディレクトリにgoogle-sa-credential.jsonという名前で保存

APIを有効にする

https://console.developers.google.com/apis/dashboard

GCPコンソールの「APIとサービス」から

  • Google Drive API
  • Google Sheets API

を検索してそれぞれ有効にする。

サービスアカウントでフォルダ、スプレッドシートを作成する

サービスアカウントでスプレッドシートを操作する際には、以下のいずれかの必要がある。

  • サービスアカウントによって作成されている
  • サービスアカウントが編集者として共有されている
  • 公開されている

(gem google driveの記述)

スプレッドシートの「共有」でサービスアカウントを許可できるならば、共有した時点でこのタスクを必要としないので飛ばしても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と似て非なるもの。バケットやオブジェクトに柔軟に権限を与えるっぽい。

その後の実装方針

  1. gem google_driveを使用する
    • spreadsheet → worksheet → json
  2. 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へ変更してレスポンスとして返すことができます!

参考

gem google-drive

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0