五倍紅寶石・專業程式教育

五倍紅寶石 ・專業程式教育機構

我把資料庫放到 Google Sheets 上了

By 卡米・7月 13 2019・技術文章
我把資料庫放到 Google Sheets 上了

大家好,我是卡米哥。今天要教大家怎麼用 Google Sheets 存放資料並且在 rails 當中使用。

在本次的教學中,我們主要的需求是想要直接以 Google Sheets 作為後台,來取代 rails 原本的資料庫以及需要自己寫的管理介面。

使用 Google Sheets 作為後台,比起自己做的管理介面,有許多好處,像是可以省下製作後台的時間,編輯速度快,支援共同編輯,支援版本管理等。

注意:在本次的教學中我們不保證資料的安全性。

建立 Google Sheet 並且發布成 CSV 格式

首先先新增一個 Google Sheet,我這邊做好了一個示範:

https://docs.google.com/spreadsheets/d/1kDEbaStqDG-g6HTTwbUx_MOWTtmHtISZzMtcinAKQqI/edit?usp=sharing

然後將這個 sheet 做發布:

點選 File

點選 Publish to the web...

點選 Entire Document 改選為 Sheet1

點選 Web page 改選為 Comma-separated values (.csv)

都選好的時候看起來像這樣:

點選 Publish 並且按下確定後:

把中間的網址複製起來,我的 csv 做好之後的連結如下:

https://docs.google.com/spreadsheets/d/e/2PACX-1vROPREvtOJD2nRGeRjXAzFgG2Qx4Zdx0DM1bLl7VsPtI8ftp7juEW7g0IPhGsXI5g-fNWdfl1erwV7M/pub?gid=0&single=true&output=csv

使用瀏覽器開啟這個網址時,會下載一個 csv 檔。若使用文字編輯器開啟這個檔案,會看到以下內容:

keyword,message
喔~,氣氣氣氣氣

請注意,任何人只要擁有這個網址,就能夠讀取到完整的資料內容。

在 Rails 讀取 CSV 並且解析為 Model

首先,先建立一個新的專案:

rails new google_sheets_demo

./app 資料夾下建立 sheets 資料夾,在 sheets 資料夾中新增一個 keyword.rb 檔案,並且輸入以下內容:

require 'net/http'
require 'csv'

class Keyword
  SHEETS_URL = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vROPREvtOJD2nRGeRjXAzFgG2Qx4Zdx0DM1bLl7VsPtI8ftp7juEW7g0IPhGsXI5g-fNWdfl1erwV7M/pub?gid=0&single=true&output=csv'

  attr_accessor :keyword
  attr_accessor :message

  def initialize(keyword: nil, message: nil)
    self.keyword = keyword
    self.message = message
  end

  def self.all
    body = Net::HTTP.get(URI(SHEETS_URL)).force_encoding('UTF-8')
    sheet = CSV.parse(body)
    title = sheet.shift.map(&:to_sym)
    sheet.map do |row|
      Keyword.new(**title.zip(row).to_h)
    end
  end
end

以上的程式碼就是在做一個類似 Model 的 all 方法,用來取得所有的資料,我大概解釋一下原理:

  1. 使用 Net::HTTP.get 取得 csv 資料
  2. 使用 CSV 解析 csv 資料
  3. 去除標題列
  4. 將資料轉換為物件模型

好的,那麼馬上來試驗一下,先進入 rails console 後輸入以下程式碼:

Keyword.all

其結果為:

 => [#<Keyword:0x00007faf28560e58 @keyword="喔~", @message="氣氣氣氣氣">]

取得了一個陣列,雖然沒辦法使用 Active Record 提供的 where 功能,但 Array 以及 Enumerable 還是有類似的內建方法可以用。

雖然取得了資料,但每次都會去抓取是不好的,畢竟後台的資料不會一直更新,而且 google sheets 的抓取時間其實蠻久的,大約要2~3秒左右。

實作簡易的快取機制

我們可以增加一個快取機制,讓他每分鐘重抓一次就好,我們對 keyword.rb 進行一些調整,以下是調整後的程式碼:

require 'net/http'
require 'csv'

class Keyword
  SHEETS_URL = 'https://docs.google.com/spreadsheets/d/e/2PACX-1vROPREvtOJD2nRGeRjXAzFgG2Qx4Zdx0DM1bLl7VsPtI8ftp7juEW7g0IPhGsXI5g-fNWdfl1erwV7M/pub?gid=0&single=true&output=csv'
  $cached_keywords = nil
  $cached_keywords_expired_at = nil

  attr_accessor :keyword
  attr_accessor :message

  def initialize(keyword: nil, message: nil)
    self.keyword = keyword
    self.message = message
  end

  def self.all
    if $cached_keywords_expired_at.nil? || $cached_keywords_expired_at < Time.current
      body = Net::HTTP.get(URI(SHEETS_URL)).force_encoding('UTF-8')
      sheet = CSV.parse(body)
      title = sheet.shift.map(&:to_sym)
      $cached_keywords = sheet.map do |row|
        Keyword.new(**title.zip(row).to_h)
      end

      $cached_keywords_expired_at = 1.minutes.since
    end

    $cached_keywords
  end
end

我們將快取儲存在整個網站的全域變數上,也就是錢字號 $ 開頭的變數上,如此一來就能夠加快查詢速度。

在每次取得 Keyword.all 的時候先檢查是否存在快取,如果不存在的話,才去抓取 CSV 以及解析成 Model 物件。

實際應用的話,我會建議將快取功能以及 Sheets 功能拆解成獨立的檔案,最理想的情況則是做成 gem,在這裡為求簡單理解,就不將問題複雜化了。

試用做好的 Model

那麼我們做個簡單的網頁介面來試用看看這個 Model。

先修改 config/routes.rb 建立一個 routes 規則指向 chatbot#talk

Rails.application.routes.draw do
  get 'talk/:say', to: 'chatbot#talk'
end

然後在 app/controllers 資料夾下建立一個 chatbot_controller.rb,內容如下:

class ChatbotController < ApplicationController

  def talk
    reply_message = Keyword.all.find{ |keyword| keyword.keyword == params[:say] }&.message
    render plain: reply_message || "蛤?"
  end
end

接著開啟 rails server 做測試,在瀏覽器輸入網址 http://localhost:3000/talk/喔~ 後,就會看到:

那麼今天的教學就到這邊,我是卡米哥,謝謝收看。