wheneverとgoogle-api-clientを使ってRailsのDBのデータをスプレッドシートに定期出力してみた

こんにちは!kossyです!



さて、今回は、cronジョブを簡単に設定できるGem「whenever」と
スプレッドシートを操作できるGem「google-api-client」を使って、
RailsのDBのデータをスプレッドシートに定期出力する方法について
ブログに残してみたいと思います。



環境
Rails 5.2.3
Ruby 2.5.1
google-api-client 0.27.1
whenever 1.0.0
MacOS Mojave




まずはwheneverの準備
github.com
まずはwheneverをRailsプロジェクトにインストールしていきます。

/.gemfile

gem 'whenever'

次にbundle installをした後に、wheneverize コマンドを実行します。

$ bundle install

$ wheneverize .

wheneverizeコマンドを実行すると、config/schedule.rbというファイルが生成されます。

config/schedule.rb

require File.expand_path(File.dirname(__FILE__) + "/environment")
rails_env = ENV['RAILS_ENV'] || :development
set :environment, rails_env
set :output, "#{Rails.root}/log/cron.log"

every :week, at: '0:00' do
  # runner 'example'
end

schedule.rbに書いたジョブをcrontabに登録する場合は、以下のコマンドをターミナルから実行します。

$ whenever --update-crontab



細かい使い方については、公式ドキュメントや
Wheneverは導入が超簡単なcrontab管理ライブラリGemです![Rails 4.2 x Ruby 2.3] | 酒と涙とRubyとRailsと

上記のサイトを参考にしてみてください。





google-api-clientの設定
次にスプレッドシートを操作するためのgemである
google-api-clientをインストールし、諸々の設定を説明します。


まずはGemファイルに追記しbundle installします。

/.gemfile

gem 'google-api-client'


$ bundle install


次にgoogleAPI ruby quickstartページにアクセスし、大まかな流れをマニュアル通りに進めます。
developers.google.com



まずは下のボタンを押して、Google Sheets APIを有効にします。
f:id:kossy-web-engineer:20190831175304p:plain



すると、
f:id:kossy-web-engineer:20190831175418p:plain
プロジェクト名とサービスおよび関連するAPIの使用を利用規約に準拠することに同意を求められるので、プロジェクト名を決め、yesにします。
有効化に成功すると、


f:id:kossy-web-engineer:20190831175643p:plain
認証情報をダウンロードできるようになるので、ダウンロードし、
Railsプロジェクトのconfig直下にでも置いておきます。



この時、gitignoreすることを忘れないで下さい。

.gitignore

config/credentials.json

次に、Step3のコードを参考にしてlib/google_api_client.rbファイルを作成します。

lib/google_api_client.rb


# 必須モジュールの宣言
require 'google/apis/sheets_v4'
require 'googleauth'
require 'googleauth/stores/file_token_store'

# 何かと便利なのでクラスにしています。
class GoogleApiClient
  OOB_URI = 'urn:ietf:wg:oauth:2.0:oob'.freeze
  APPLICATION_NAME = 'Your App Name'.freeze
  CREDENTIALS_PATH = Rails.root + 'config/google_drive_credentials.json'.freeze
  # 最初にタスクを実行する時に自動で作成されます。
  TOKEN_PATH = Pathname.new Rails.root + 'config/google_drive_token.yaml'.freeze
  SCOPE = Google::Apis::SheetsV4::AUTH_SPREADSHEETS

  # インスタンスが作成された時に実行
  def initialize
    @service = Google::Apis::SheetsV4::SheetsService.new
    @service.client_options.application_name = APPLICATION_NAME
    @service.authorization = authorize
  end

  def authorize
    client_id = Google::Auth::ClientId.from_file CREDENTIALS_PATH
    token_store = Google::Auth::Stores::FileTokenStore.new file: TOKEN_PATH
    authorizer = Google::Auth::UserAuthorizer.new client_id, SCOPE, token_store
    user_id = 'default'
    credentials = authorizer.get_credentials user_id
    if credentials.nil?
      url = authorizer.get_authorization_url(base_url: OOB_URI)
      puts 'Open the following URL in the browser and enter the ' \
           "resulting code after authorization:\n" + url
      code = gets
      credentials = authorizer.get_and_store_credentials_from_code(
        user_id: user_id, code: code, base_url: OOB_URI
      )
    end
    credentials
  end

  # スプレッドシートのアップデート用メソッド
  def update_sheets(data, spreadsheet_id:, sheet_name: nil)
    value_range = Google::Apis::SheetsV4::ValueRange.new
    value_range.range = "#{sheet_name}!A1:ZZ"
    value_range.values = data
    @service.clear_values spreadsheet_id, value_range.range
    @service.update_spreadsheet_value spreadsheet_id, value_range.range, value_range, value_input_option: 'USER_ENTERED'
  end
end

Railsではlib下のファイルはapplication.rbに変更を加えないと、自動で読み込んでくれないので、config/application.rbに以下の設定を追加します。

config/application.rb

もろもろ省略
module App
  class Application < Rails::Application

    config.paths.add 'lib', eager_load: true <= 追加
  end
end

次に、新しいスプレッドシートを作成して、スプレッドシートのIDを控えておきます。
IDはURLの/d/の後に続く数字とアルファベットが混在した部分までです。

このスプレッドシートのIDを、

$ EDITOR="vim" rails credentials:edit

環境変数として登録します。

spread_sheet_id: <your spreadsheetid>

次に、lib/cron_task.rbというファイルを作成します。このファイルに、実際にcronジョブに実行してもらうことを書き込んでいきます。

lib/cron_task.rb

Module CronTask

def self.sample_task
  # Memberモデルがあることを想定しています。以下は例です。

    data = [['背番号', 'ユーザー名', '氏名', 'メールアドレス', '誕生日', '性別', '出身地']]
    Member.all.decorate.each do |member|
      data << [
        member.number,
        member.name,
        member.full_name,
        member.email,
        member.birthday,
        member.show_sex,
        member.prefecture.name
      ]
    end
  # スプレッドシートのアップデート用メソッドを呼び出す。IDは環境変数で設定した値を使う。sheet_nameでシートの名前を設定できる。
    GoogleApiClient.instance.update_sheets data, spreadsheet_id: "#{Rails.application.credentials.dig(:spread_sheet_id)}", sheet_name: 'メンバー一覧'
end

そして、schedule.rbにrunnerとしてsample_taskメソッドを登録します。

config/schedule.rb

every :week, at: '0:00' do
  runner 'CronTask.sample_task'
end

その後、

$ whenever --update-crontab

を実行します。(本番環境の場合、capistranoの自動デプロイにcronジョブを追加する処理を入れていれば不要。)

これで、まずはrails c をしてコンソールから実行してみます。

$ rails c

$ CronTask.sample_task

すると、

Open the following URL in the browser and enter the resulting code after authorization:
https://accounts.google.com~~~

と表示されるので、表示されたURLにアクセスします。

アクセスすると、ログインを要求されるので、アカウント選択しログインします。
f:id:kossy-web-engineer:20190831191101p:plain

ログインすると、
f:id:kossy-web-engineer:20190831191138p:plain
この画面が表示されるので、左下の「詳細」をクリックし、「Quickstart(安全ではないページ)に移動」をクリックします。

すると、
f:id:kossy-web-engineer:20190831191304p:plain
この画面に遷移するので、「許可」をクリックします。

クリック後、
f:id:kossy-web-engineer:20190831191351p:plain
選択内容を確認してくださいという画面が表示されるので、これも「許可」をクリックします。

すると、トークンが発行されるので、そのトークンをコピーして、コマンドラインにペーストして、enterキーを押します。
その後、スプレッドシートに値が吐き出され、config/google_drive_token.yamlというファイルが作成されます。

このファイルはgitignoreするのを忘れないようにしましょう。

./.gitignore

/config/google_drive_token.yaml

今回はローカル環境のDBのデータを吐き出しましたが、本番環境での吐き出し方も
後々ブログに残してみたいと思います。



参考にさせていただいたサイト
Ruby Quickstart  |  Sheets API  |  Google Developers
GitHub - javan/whenever: Cron jobs in Ruby
Wheneverは導入が超簡単なcrontab管理ライブラリGemです![Rails 4.2 x Ruby 2.3] | 酒と涙とRubyとRailsと

SequelProでレコードを削除しても、dependent: :destroyしている先のレコードは消えないよ

こんにちは!kossyです!




www.sequelpro.com


何を当たり前なことを、、、
といったタイトルですが、開発環境で実装している時に遭遇しました。笑
なぜ意味不明なデータが出現するんや!?!?!?
と30minほど悩みましたが、SequelProでレコードを削除しても関連するレコードは消えないことに気がつき、
「自分はなんてアホなことで無駄な時間を使ったんだ」と悔やんでしまったわけです。

あくまでdependent: :destroyはrails c してコンソール上で実行する時や、API叩いてdeleteアクションを叩いた時しか
実行されないわけで、灯台元暗しというか、基本的なことを忘れていたわけです。


明日から気をつけます、、、

ポケモンのデータをDBで表現する

こんにちは!kossyです!



昨日、「ポケモンのデータをDBに保存する」
というブログを書いたのですが、肝心のDB定義を忘れていました。


class CreatePokemons < ActiveRecord::Migration[5.2]
  def change
    create_table :pokemons do |t|
      t.integer :number, null: false, unique: true
      t.string :name, null: false, unique: true
      t.string :type_1, null: false
      t.string :type_2
      t.integer :hp, null: false
      t.integer :attack, null: false
      t.integer :defense, null: false
      t.integer :sp_attack, null: false
      t.integer :sp_defense, null: false
      t.integer :speed, null: false
      t.integer :total, null: false

      t.timestamps
    end

    add_index :pokemons, :number
    add_index :pokemons, :name
    add_index :pokemons, :type_1
    add_index :pokemons, :type_2
  end
end

トータル種族値をカラムに保存するか、全てを足して表示するかで悩んだのですが、
DBに保存した方が何かと便利かなと思い、カラムを用意することにしました。