試行錯誤

まだ全然中身がないので色々実験している。相変わらず本題には入れていない。

私は多数派の安心感を求めて長いものには積極的に巻かれに行く人間なので、RubyCGI としてどのような構成が美しいとされるのかガイドラインのようなものがあればとても助かるのだが、考えてみたらそんなものがあるならフレームワークが乱立したりはしていないわな。

index.rb

まず CGI オブジェクト と CGI::Session オブジェクトを引数で持ち回すのをやめてグローバル変数に放り込んだ。

#!/usr/local/bin/ruby -Ku

require 'cgi'
require 'cgi/session'
require 'config'
require 'vote'

class CGI
  def redirect(location)
    puts header({'Status' => '302 Found', 'Location' => location,})
  end
end

File.umask(0066)
Dir.mkdir(USER_FILE_PATH) unless File.exists?(USER_FILE_PATH)

$cgi = CGI.new
$session = CGI::Session.new($cgi, {'session_expires' => Time.now + (60 * 60 * 24 * 5)})

Vote.new.run

私はなるたけグローバルフリーに考える癖が付いているのだが、Diplomacy MOE では盲目的にこだわりすぎて現在進行形で酷い目に遭っているので方針変更。

vote.rb

認証関係の処理は Auth オブジェクトに丸投げ。これで Vote クラスはすっきりした。今後追加される処理もこの調子で委譲しまくれば肥大化は防げそうだ。

require 'forwardable'
require 'auth'
autoload 'TopPage', 'toppage'
autoload 'EditUserPage', 'edituserpage'
autoload 'RegisterPage', 'registerpage'
autoload 'InformationPage', 'informationpage'

class Vote

  extend Forwardable

  def_delegators :@auth, :action_login, :action_logout
  def_delegators :@auth, :action_edituser, :action_edituser_execute
  def_delegators :@auth, :action_register, :action_register_confirm, :action_register_execute

  def initialize
    @auth = Auth.new
  end

  def run
    action = $cgi['action'].to_s
    if action.size > 0
      send('action_' + action)
    else
      TopPage.new(@auth).show
    end
  end

  def method_missing(name, *args)
    $cgi.redirect(BASE_URL)
  end

end

委譲は Forwardable ライブラリを使用。ついでにメソッド名にはリクエストの action パラメータに対応していることをプレフィクスで明示してみた。

auth.rb

Vote クラスから委譲された処理と本来の処理とで今度はこっちが膨れ上がってしまった。もっともこれ以上機能が増える予定はないし、空行込みで 100 行強なら許容範囲として良かろう。

require 'erb'
require 'yaml/store'
require 'verifyer'

class Auth

  include Verifyer

  attr_reader :email, :name

  def initialize
    @email = $session['email'].to_s
    if @email.size > 0 && File.exists?(USER_FILE_PATH + @email)
      db = YAML::Store.new(USER_FILE_PATH + email)
      db.transaction(true) do
        @name = db[:name]
        $session['edituser_name'] = @name unless $session['edituser_name'].to_s.size > 0
      end
    else
      @email = nil
    end
  end

  def to_html
    ERB.new(File.read('auth.rhtml'), nil, '-').result(binding)
  end

  def logon?; @email.to_s.size > 0 end

  def action_login
    return $cgi.redirect(BASE_URL) unless $cgi.referer
    vote_email = $cgi['vote_email'].to_s
    vote_pass = $cgi['vote_pass'].to_s
    begin
      login(vote_email, vote_pass)
      $cgi.redirect($cgi.referer)
    rescue => e
      info = InformationPage.new(self)
      info.message = e.message
      info.show
    end
  end

  def login(email, pass)
    email = verify_email(email)
    pass = verify_pass(pass)

    unless File.exists?(USER_FILE_PATH + email)
      raise 'メールアドレスが登録されていません。'
    end

    db = YAML::Store.new(USER_FILE_PATH + email)
    db.transaction(true) do
      if db[:pass] != pass
        raise 'パスワードが違います。'
      end
    end
    $session['email'] = email
  end

  def action_logout
    $session.delete
    $cgi.redirect($cgi.referer)
  end

  def action_edituser
    return $cgi.redirect(BASE_URL) unless $cgi.referer
    return $cgi.redirect(BASE_URL) unless logon?
    edit = EditUserPage.new(self)
    edit.error = $session['edituser_error'] if $session['edituser_error'].to_s.size > 0
    edit.show
    $session['edituser_error'] = nil
  end

  def action_edituser_execute
    return $cgi.redirect(BASE_URL) unless $cgi.referer
    name = $cgi['edituser_name'].to_s
    pass1 = $cgi['edituser_pass1'].to_s
    pass2 = $cgi['edituser_pass2'].to_s
    begin
      $session['edituser_name'] = name
      EditUserPage.new(self).execute(name, pass1, pass2)
      $session['edituser_name'] = nil
      return $cgi.redirect(BASE_URL)
    rescue => e
      $session['edituser_error'] = e.message
      return $cgi.redirect(BASE_URL + '?action=edituser')
    end
  end

  def action_register
    return $cgi.redirect(BASE_URL) unless $cgi.referer
    if logon?
      $cgi.redirect(BASE_URL + '?action=edituser')
    else
      reg = RegisterPage.new(self)
      reg.error = $session['register_error'] if $session['register_error'].to_s.size > 0
      reg.show
      $session['register_error'] = nil
    end
  end

  def action_register_confirm
    return $cgi.redirect(BASE_URL) unless $cgi.referer
    vote_email = $cgi['vote_email'].to_s
    begin
      reg = RegisterPage.new(self)
      reg.confirm(vote_email)
      reg.show
    rescue => e
      $session['register_error'] = e.message
      $cgi.redirect($cgi.referer)
    end
  end

  def action_register_execute
    return $cgi.redirect(BASE_URL) unless $cgi.referer
    vote_email = $cgi['vote_email'].to_s
    reg = RegisterPage.new(self)
    reg.execute(vote_email)
    reg.show
  end

end

登録情報の保存には YAML::Store を使用。アカウントのメールアドレスがそのままファイル名になる。to_html メソッドはほぼ全画面共通のログインフォーム表示用。エラー処理での例外の使い方がかなりお行儀悪いのでどうにかしないといけない。独りで作っていると異常ルートがついつい雑になってしまうのが困りもの。リファラチェックもやるならもう少し真面目にやらないと危ない気がする。


この調子でソースを貼っていけばいくらでも行数が稼げそうだが、まず誰も読んではくれまいな。本日はこれまで。続きは……ないかも知れん。


プログラミングRuby 第2版 言語編

プログラミングRuby 第2版 言語編