応募フォームを作ったりアンケートを取ったり、その度にアプリケーションを開発するのは面倒なので、Google Spreadsheetsを使うことにした。
しかしどうもGoogle Formとか言うのは使いづらいし、もうちょっと何とかならんかなーと思ったので試作してみた。
こんな感じ
[画像:gss_00]
↑管理者は空っぽのSheetを開いてじっと見ています。
[画像:gss_01]
↑利用者は管理者が設置したサイト上にあるこんなhtmlに適当な値を入れてSubmitします。Submit後、利用者は「ありがとうございました」ページを見て立ち去ることでしょう。
[画像:gss_02]
↑管理者は、その値がシートにスポンと入るのを目撃します。自動更新されるみたいです。良くできてる…。
こんなのを作ろうと言うわけです。
調べていたらGoogle SpreadsheetsをActiveResourceのように扱う素敵なコードを発見したので利用することにしました。コードもまとまっていて、使いやすさも良かったので、即決。
やることは3ステップ。
- Google Spreadsheetsに格納先シートを用意する
- htmlを用意する
- 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)
config/initializers内で設定を行うようになっていますよ