RJSなどのAjax処理中に例外が発生した場合に、通常の例外画面を出したい場合

app/controllers/application.rbで以下のようにかくとできます(もしくは、libでうまく上書きするか)。

def rescue_action_locally(exception)
  add_variables_to_assigns
  @template.instance_variable_set("@exception", exception)
  @template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
  @template.send!(:assign_variables_from_controller)
  
  @template.instance_variable_set("@contents", @template.render_file(template_path_for_local_rescue(exception), false))
  
  response.content_type = Mime::HTML
  
  if request.xhr?
    render(:update) do |page|
      controller = @template.controller
      page.remove :head
      page.replace_html :body, controller.send(:render_for_file, controller.send(:rescues_path,"layout"), controller.send(:response_code_for_rescue,exception))
    end
  else      
    render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
  end
end

def rescue_action_in_public(exception)
  request.xhr? ? render(:update) {|page| page.redirect_to('適当なエラー用view')} : render(:template => '適当なエラー用view')
end

注意点

layout/application.html.erbあたりで、

<head id="head">
...
</head>
<body id="body">
...
</body>

のようにidを付加していないといけません。

やっていること

Ajaxリクエスト時には、ただ単にrenderを使ってもAjaxリクエストの戻りをrenderするだけなので、少し手をくわえないといけません。
ローカル稼動時には、headを消してcssを無効化し、bodyの内容をrails内部の例外ページに書き換えます。
本番稼動時には、アプリ毎の決まった例外ページに飛ばします。

Railsでトランザクショントークン(ダブルサブミット、2重登録対策)を使う

double_submit_protectionを使いましょう。
ただし、2008/9/11現在、sessionをクリアしていないというバグ?があります。なので、
http://github.com/DianthuDia/double_submit_protection/
な感じでgithubの使い方を学びながら軽く修正してみました。
インストールは

 script/plugin install git://github.com/DianthuDia/double_submit_protection.git

で行います。

使い方は、REAMME.txtにあるように、

View

<% form_for do |f|%>
....
<%=  double_submit_token %>
<% end %>

Controller

def create
  if double_submit?
    エラー処理
  end
  ...
end

な感じで使います。

追記

id:kelkronsoさんから、before_filterを使えばよいんじゃないかと提案を受けました。エラー処理が共通ならばそちらのほうが良いです。

filter使う場合

before_filter :double_submit?, :only=>[:create]
def create
  ...
end

もちろん、double_submit?内で例外処理を追記しないといけません。

def double_submit?
  session_token = session[SESSION_TOKEN_KEY]
  session[SESSION_TOKEN_KEY] = nil
  raise '二重登録です' if request.post? && (session_token != params[TOKEN_FIELD_NAME])
end

ActiveRecord::Errorsにあえて、削除やマージをつけてみる

<9/5 23:38 修正><10/15 23:36 修正2>

  • lib/extension.rb
module Extension  
  def included( recipient )
    recipient.extend self::ClassMethods
    _this_class = self
    recipient.class_eval { include _this_class::InstanceMethods }
  end
  alias_method :extended, :included
end
  • lib/errors_extension.rb
module ErrorsExtension
  extend Extension

  # クラスメソッド定義
  module ClassMethods
  end

  # インスタンスメソッド定義
  module InstanceMethods
    def +(other)
      self.instance_variable_get('@errors').merge other.instance_variable_get('@errors')
    end
    alias_method(:merge, :'+')

    def delete(key)
      self.instance_variable_get('@errors').delete(key)
    end
    
    def merge!(other)
      self.instance_variable_get('@errors').merge!(other.instance_variable_get('@errors'))
    end
  end
end

ActiveRecord::Errors.extend ErrorsExtension
  • config/initializers/errors_extension.rb
require 'errors_extension'

モデルでhelperを使う方法

$(RAILS_ROOT)/helpers/model_helper.rb

module ModelHelper
  #インスタンスメソッド
  def validate_name
  end
  
  def self.append_features(base) # :nodoc:
    super
    base.extend ClassMethods
  end
  
  module ClassMethods
    # クラスメソッド
    def convert
    end
  end
end

みたいなhelperを定義し、
$(RAILS_ROOT)/helpers/foobar.rb

class Foobar < ActiveRecord::Base
  include ModelHelper
end

で使用できる。

Foobar.convert
Foobar.new.validate_name

PassengerとcapistranoでのデプロイTips

デプロイの流れ(インストールは省略)

cd RAILS_ROOT
capify .
# deploy.rbを下記のものに変更
cap deploy:setup # :deploy_toにcapistranoに必要なフォルダを作る
cap deploy:cold  # SVNから取得+migration+Passenger再起動(RAILS_ROOT/tmp/restart.txtが作成される)
cap deploy       # SVNから取得+Passenger再起動

cap deploy:restart # Passenger再起動

deploy.rb

<追記> http://www.slideshare.net/moro/capistrano-in-practice-webcareer-presentation?src=embed を参考に更新しました

#############################################################
#	Application
#############################################################

set :application, '<app_name>'
set :deploy_to, "/var/www/#{application}"

#############################################################
#	Deploy user
#############################################################

set :runner, '<run_user>'
set :use_sudo, true

set :user do Capistrano::CLI.ui.ask('SSH User: ') end
set :password do Capistrano::CLI.password_prompt('SSH Password: ') end

#ssh_options[:verbose] = :debug

#############################################################
#	Servers
#############################################################

role :app, '<deployed server>'
role :web, '<deployed server>'
role :db,  '<deployed server>', :primary => true

#############################################################
#	Subversion
#############################################################

set :scm_user do Capistrano::CLI.ui.ask('SVN User: ') end
set :scm_password do Capistrano::CLI.password_prompt('SVN Password: ') end
set :repository_uri, '<repo_uri>'

set :repository do "--username #{scm_user} --password #{scm_password} --no-auth-cache  #{repository_uri}" end

#############################################################
#	after deploy:setup hooks
#############################################################
after 'deploy:setup' do
  try_sudo "chown -Rf #{runner}:#{runner} #{deploy_to}"
end

#############################################################
#	Override existing recipes
#############################################################

namespace :mod_rails do
  desc <<-DESC
  Restart the application altering tmp/restart.txt for mod_rails.
  DESC
  task :restart, :roles => :app do
    run "touch  #{File.join(deploy_to, current_dir)}/tmp/restart.txt"
  end
end

namespace :deploy do
  %w(start restart).each { |name| task name, :roles => :app do mod_rails.restart end }
end

な感じでかいておく。参考と違うのは、

cap deploy:start
cap deploy:restart

も使えるようにした点。

最低限必要なもの

サーバ
  • Apache2
  • Ruby
  • Passenger
  • svnクライアント
  • sshサーバ
  • svn取得先にアクセスできる事
クライント(デプロイ作業に使うマシン)
  • Ruby
  • サーバにSSHで接続できること(SSHクライアントはgemのを使うためいらない)
  • svnクライント
  • svn取得先にアクセスできる事

クライントに、svnが必要な理由はサーバにデプロイ指示するときに、最新のrev番号を渡しているからです。最新のrevを取得する為にsvn infoを使っているというわけです。

注意点

パーミッションの問題

Railsアプリケーションのディレクトリ($RAILS_ROOT)および$RAILS_ROOT以下のファイル・ディレクトリのオーナーは、root以外としてください。

Passengerでは、root権限でRailsアプリケーションを動かすことはできません。Railsアプリケーションが動作するユーザーは$RAILS_ROOT/config/environment.rbのオーナーとなりますが、environment.rbがrootの場合はnobodyユーザーで動作します。このとき、オーナーがrootである$RAILS_ROOT/logディレクトリや$RAILS_ROOT/tmpディレクトリに書き込みができず、アプリケーションが動作しないことがあります。

最高のリーダー、マネジャーがいつも考えているたったひとつのこと を読んで 其の二

最高のリーダー、マネジャーがいつも考えているたったひとつのこと

http://d.hatena.ne.jp/DianthuDia/20080810/1218370476 の続きです。

続きを読む

DBからYAMLにする方法(ar_fixtures)

今までは、ar_fixtures+wamlを使っていましたが、もっとよいものを見つけました。

http://d.hatena.ne.jp/elm200/20070928/1190947947

id:elm200さんに感謝です。手で書いた時のように綺麗に出力されます。

ar_fixtures+waml

dog_00301: 
  name: "ああああ"
  lock_version: 0
  id: 301
  name_kana: "アアアア"
  created_at: 2008-08-26 08:47:25 Z
  updated_at: 2008-08-26 08:47:25 Z

extract_fixtures.rake

dog301:
  id: 301
  name: ああああ
  name_kana: アアアア
  lock_version: 0
  created_at: 2008-08-26 08:47:25
  updated_at: 2008-08-26 08:47:25