[フレーム]

やむにやまれず

2006年創業の会社を経営する元プログラマ。現在従業員12名(内7名が欧米人)で元気にお仕事中。今はもうコードは書いてないので、いつか復帰したい。@sparklegate

応募フォームを作ったりアンケートを取ったり、その度にアプリケーションを開発するのは面倒なので、Google Spreadsheetsを使うことにした。
しかしどうもGoogle Formとか言うのは使いづらいし、もうちょっと何とかならんかなーと思ったので試作してみた。

こんな感じ

[画像:gss_00]
↑管理者は空っぽのSheetを開いてじっと見ています。

[画像:gss_01]
↑利用者は管理者が設置したサイト上にあるこんなhtmlに適当な値を入れてSubmitします。Submit後、利用者は「ありがとうございました」ページを見て立ち去ることでしょう。

[画像:gss_02]
↑管理者は、その値がシートにスポンと入るのを目撃します。自動更新されるみたいです。良くできてる…。

こんなのを作ろうと言うわけです。
調べていたらGoogle SpreadsheetsをActiveResourceのように扱う素敵なコードを発見したので利用することにしました。コードもまとまっていて、使いやすさも良かったので、即決。

やることは3ステップ。

  1. Google Spreadsheetsに格納先シートを用意する
  2. htmlを用意する
  3. Controller(Rails)を用意したりする

Google Spreadsheetsに格納先シートを用意する

これは簡単。Spreadsheetドキュメントを作成して、Sheetを使うだけ。Excelを知っている人なら説明なしにやれるでしょう。1行目に欲しいデータセットのカラム名を列挙していきます。
今回は hoge, fuga, moke, created と書いてみました。
ちなみに、Web API経由だとアンダースコアは指定できないようなので、英数字だけでカラム名を付けると良いと思います。

htmlを用意する

app/views/gss/form_sample.erb

<html>
 <head>
 <title>Sample Form</title>
 </head>
 <body>
 <form method="post" action="append">
  <%= token_tag %>
  <input type="hidden" name="target_sheet" value="sample" /><!-- シートの指定 -->
  <input type="hidden" name="thanks_page" value="thanks_sample" /><!-- 画面遷移先 -->
  <!-- gsx_* はカラム名に対応する -->
  <input type="text" name="gsx_hoge" value="" />
  <input type="radio" name="gsx_fuga" value="A" />
  <input type="radio" name="gsx_fuga" value="B" />
  <select name="gsx_moke">
  <option value="OPT1">OPT1</option>
  <option value="OPT2">OPT2</option>
  <option value="OPT3">OPT3</option>
  </select>
  <input type="hidden" name="gsx_created" value="auto" /><!-- オマケ機能(後述) -->
  <input type="submit" name="go" value="submit" />
 </form>
 </body>
</html>

Controller(Rails)を用意したりする

app/controller/GssController.rb

require 'ares_google_spreadsheets'
class GssController < ApplicationController
 include Environment
 def method_missing(method)
  if(method.to_s.match(/(form_.*)|(thanks_.*)/))
   filename = params[:controller]+'/'+method.to_s
   render :file => filename, :use_full_path => true
  else
   raise NoMethodError.new(method.to_s)
  end
 end
 def append
  GoogleSpreadsheets::Base.user   = Settings.credential[:user]
  GoogleSpreadsheets::Base.password = Settings.credential[:password]
  records = {}
  params.each { |key, value|
   name = key.to_s.match(/gsx_(.*)/)
   if name
    method = Settings.auto_insertion[name[1]]
    value = method.call(value) if method
    records[key] = value
   end
  }
  target_sheet_key = params[:target_sheet]
  raise MissingParameterError.new('Please set \'target_sheet\' value in your html.') unless target_sheet_key
  target_sheet = Settings.target_sheet[target_sheet_key]
  raise TargetSheetNotFoundError.new('Please check Environment.target_sheet values.') unless target_sheet
  raise TargetSheetNotFoundError.new('Please check document_name and sheet_name in Environment.target_sheet.') unless
   (target_sheet.has_key?(:document_name) || target_sheer.has_key?(:document_id)) &&
   (target_sheet.has_key?(:sheet_name)  || target_sheet.has_key?(:sheet_id))
  doc_id = target_sheet[:document_id]
  if !doc_id
   docs = GoogleSpreadsheets::Spreadsheet.find(:all) || []
   docs.each { |doc|
    if(doc.title==target_sheet[:document_name])
     doc_id = doc.id
     break
    end
   }
   raise DocumentNotFoundError.new("document_name '#{target_sheet[:document_name]}' is not available.") unless doc_id
  end
  sheet_id = target_sheet[:sheet_id]
  if !sheet_id
   sheets = GoogleSpreadsheets::Worksheet.find(:all, :params => {
    :document_id => doc_id,
    :visibility => 'private',
    :projection => 'full'
   }) || []
   sheets.each { |sheet|
    if(sheet.title==target_sheet[:sheet_name])
     sheet_id = sheet.id
     break
    end
   }
   raise SheetNotFoundError.new("sheet_name '#{target_sheet[:document_name]}' is not available.") unless sheet_id
  end
  new_row = GoogleSpreadsheets::List.new({
   :document_id => doc_id,
   :worksheet_id => sheet_id,
   :visibility  => 'private',
   :projection  => 'full'
  }.merge(records))
  new_row.save
  redirect_to :action => (params[:thanks_page] || 'thanks_default').split(/\./)[0]
 end
 class MissingParameterError < StandardError; end
 class TargetSheetNotFoundError < StandardError; end
 class DocumentNotFoundError < StandardError; end
end

config/environment.rb (設定ファイルなので、場所はenvironment.rbじゃなくても良いです)

require 'time'
module Environment
 class Settings
  def self.credential
   {
    :user   => 'your_account@gmail.com',
    :password => 'secret_password',
   }
  end
# formのtarget_sheetに指定されるもので、シートを特定する設定を書きます def self.target_sheet { 'sample' => {
# 名前で指定する場合は以下を指定 :document_name => 'Sample Sheet', :sheet_name => 'Sheet1', ## もしIDが分かっている場合は以下を指定
## 検索クエリを出さない分、高速です # :document_id => '...', # :sheet_id => '...', }, } end def self.auto_insertion { 'created' => Proc.new { Time.now.rfc2822 }, } end end end

オマケ機能の説明

htmlでgfx_createdなどカラム名が指定されると、それに対応するProcが起動して値を自動的に上書きする機能が付いています。
例えば記録時間とかそのタイミングで決まるものなんかはここに定義しておけば良いかも。

あと、viewの命名規則があって、form_*.erbとthanks_*.erbは用意さえすればGssControllerが勝手に表示します。

欠点

サーバサイドで入力チェックを全くやっていないので、その辺機能不足です。値はGoogle Spreadsheetsにそのまま渡されるので、文字の最大長はそこで決まります。その他セキュアじゃないとか何かありそうなので、本気で使おうと思っている方は十分な検証をお願いします。

コメント一覧 (1)

    • 1. ぽーく
    • 2009年12月18日 14:37
    • Rails2系(もしかしたら、2.2以降)は
      config/initializers内で設定を行うようになっていますよ

コメントする

名前
最新記事
人気記事
QRコード
traq

AltStyle によって変換されたページ (->オリジナル) /