Rails つまみぐい

読者です 読者をやめる 読者になる 読者になる

Rails つまみぐい

Ruby on Rails 初学者による行き当たりばったりなメモ

layoutを使い分ける


HTMLで表示をする場合、デフォルトのlayoutとしてapp/views/layouts/内のapplication.html.erbが使用されます。1つのRailsアプリケーション内で、複数のlayoutを使い分けたくなった場合のやり方についてのメモ。

renderする際にlayoutを指定

controllerの中でrenderする際にlayoutを指定できます。デフォルトのlayoutとは別のlayoutとしてapp/views/layouts/にanother_layout.html.erbを用意した場合、このように書きます。

respond_to do |format|
  format.html { render :layout => "another_layout"}
end

controllerごとlayoutを指定

1つのcontrollerの中で(すべてのaction共通で)another_layoutを使うように指定することもできます。

class UserController < ApplicationController
  layout "another_layout"

  def index
    ...
  end


layoutを使いたくない場合

また、layoutを使いたくない場合もあります。例えば、ajaxリクエストを使用して部分的なHTMLを送出する場合などです。layoutを使わない場合は下のようにすればOKです。

respond_to do |format|
  format.html { render :layout => nil }
end

Railsでajaxリクエストが二重に送信されてしまう

Ruby on Rails v3.2.11
jquery-rails v2.0.1


フォームからajax送信する際に、送信ボタンを2回押しているわけではないのですが、なぜか毎回2回リクエストが送信されてしまいます。ajax送信は以下のような感じ(一部抜粋)でやっています。

<%= form_tag(:action => :scan, 
             :data-remote => true,
             :data-type => :html,
             :id => "my_form")  do %>
    <%= text_field_tag 'name'  %>
    <%= submit_tag "submit" %>
<%- end -%>

def scan
    render :layout => false
end

$(function(){
    $("my_form").on('ajax:success', function(data, status, xhr) {
        $("#result_area").html(status);
        alert("Finish!");    //テスト用
    });
});

こんな感じでやってますが、alertのウィンドウが2回表示されてしまいます。今回は参照のリクエストなのであまり影響はないですがどうも気持ち悪い。

原因は?

ウェブで同じような状況を探してみると、アセットパイプラインの取り扱いによって(?) 同じjavascriptファイルが二重に読み込まれてしまうケースがある、それによって1つの要素に対して.bind系(.live とか .on とか .delegate)が二重にセットされてしまう、ということが原因のようです。

自分のコードを見直してみて、application.jsの中の //= require_tree . を消して1つ1つ直接名前で指定したり、public/assetsを消したり(developmentモード)と色々やってみたのですが、ダメ…(x_x)

(問題の箇所をうまく見つける方法が分かりません。誰か教えてください。)


むりくり対策

よく分からないので、とりあえず二重にリクエストが飛んだときに2回目の処理をキャンセルするという、いささか強引な方法で対策してみました。

  • 1回目のajax:beforeSendが送信された時に、form要素の属性として'ajax-processing'(名前はなんでもいいです)をくっつけます。
  • 2回目のajax:beforeSendではform要素の'ajax-processing'属性をみてreturn false;して以降の処理をキャンセルします。
  • 処理終了後にajax:completeが送信された際に'ajax-processing'属性を取り除きます。

$(function(){
    $("form[data-remote=true]").on( 'ajax:success',
        function(data, status, xhr) {
            if ($(this).attr('ajax-processing')) {
                return false;
            } else {
                $(this).attr('ajax-loading', true);
            }
        }
    );

    $("form[data-remote=true]").on('ajax:complete',
        function() {
            $(this).removeAttr('ajax-processing');
        }
    );
});


あまり根本的な解決でないところがすっきりしませんが、1回しか処理されなくなったので一応よしとしておきます。

Railsでログローテーションする方法

Railsアプリケーションを動かしてるとログファイルがどんどん大きくなっていきます。ログファイルが巨大化しすぎると開くのに時間がかかっちゃいますし、何よりも大切なディスクスペースを圧迫してしまいます。

そこでログローテートです。定期的にログファイルを分割したり、古いのを捨てていったりするわけです。

ログファイルを指定サイズで分割

config/environments/production.rbファイル内で

config.logger = Logger.new("log/production.log", 5, 10 * 1024 * 1024)

とセットすると、ログファイルのサイズが10MBを超える毎に新しいログファイルを自動的に作って、古いログファイルをproduction.log.0とかproduction.log.1とかの名前にしていって、5ファイルを超える分を削除していってくれます。

ログファイルを日数で分割

config/environments/production.rbファイル内で

config.logger = Logger.new("log/production.log", 'daily')

config.logger = Logger.new("log/production.log", 'weekly')

config.logger = Logger.new("log/production.log", 'monthly')

などとします。production.log.20130120といったファイルが次々とつくられますが、こちらは自動で削除されていかないようです。

ログレベルの設定について

こちらの記事(ログに書き出すログレベルを変更する - Rails つまみぐい)で書いたようにログレベルはconfigファイルで変更ができますが、上記のように config.logger = Logger.new とした場合についてはRails.logger.levelで設定します。

Rails.logger.level = Logger::WARN   # Rails.logger.level = 2 でもOK

config.log_levelでは設定できません(設定しても何も変わりません)。要注意です。

コードに日本語を書くとエラーになる件


Controller や Helper など ruby のコードの中に日本語を書くと

invalid multibyte char (US-ASCII)

というようなエラーが出ます。

変数名やメソッド名だけでなく、文字列として埋め込んでもダメです。うっかりデバッグ用に日本語を埋め込むとはじかれてしまいます。

このエラーを回避する方法は、各ファイルの先頭に

# encoding: utf-8

もしくは

# -*- encoding: UTF-8 -*-

というおまじないを書けばOKです。


ちなみに、コメント文であれば日本語問題なく通ります。View なファイルの中も大丈夫です。

ま、そもそもコードの中にがっつり固定文字列を埋め込むのはよろしくないので、 「i18n 化をきちんとやりなさい」というありがたい天のお告げだと思うことにします。(^^)

デバッグ用の文字列をログファイルに書き出す

ログファイルに書き出す

Railsの実行時に処理されるコードの中で

logger.debug("ログに吐き出す文字列")

と書いておけば、ログファイル log/development.logに書き出されます(実行モードがdevelopmentのとき限定)。

ログに書き出される情報は以下の5つのレベルのどれかに分類されていて、どのレベルでログを書き出すかで、使用するメソッドが異なります。

:debug logger.debug (デバッグ情報)
:info logger.info (一般的な情報)
:warn logger.warn (警告情報)
:error logger.error (エラー情報)
:fatal logger.fatal (致命的なエラー情報)


なお、productionモードではデフォルトの設定だとlogger.debugは記録されません。ログの内容にはレベルがありproductionモードでは実行に影響ないものは残さない、ということなのでしょう。

ですのでproductionモードでログに書き出したい場合はlogger.infoなぞを使って

logger.info("ログに吐き出す文字列")

と書けばよいでしょう。

オブジェクトの中身を記録する

デバッグしていると、このオブジェクトの中身はどうなっているんだろう?ってことがよくあります。

文字列や数字の変数ならそのまま var.to_s や "%s"%var で文字列化してしまえばいいですが、オブジェクトでそれをやると #<ClassName:0xXXXXXXX> みたいな訳分からん文字になってしまいます。

そんなときは .inspect を使うと便利。

logger.debug(var.inspect)


パスワードなどをログに残さないようにしよう

デフォルトの設定で、アクセスがあったときに与えられたパラメータがログファイルに書き出されます。

Started POST "/users" for 192.168.1.20 at 2012-01-01 00:00:00 +0900
Processing by UsersController#index as HTML
  Parameters: {"username"=>"Oratok", "password"=>"[FILTERED]"}
Completed 200 OK in 149ms (Views: 148.3ms | ActiveRecord: 0.0ms)

3行目の"password"の値が[FILTERED]となって隠されています。この設定はデフォルトでconfig/application.rbファイル内で書かれています。

module RailsApp
  class Application < Rails::Application
    config.filter_parameters += [:password]
  end
end

"password"というキーに反応して隠しているわけです。この行を消すとアクセスしたユーザーが入力したパスワードがログ上にもろにでてしまうわけです。

隠す対象のキーを追加したい場合はこんな感じで。

module RailsApp
  class Application < Rails::Application
    config.filter_parameters += [:password, :passwd]
  end
end


参考サイト

ログに書き出すログレベルを変更する


ログ情報のレベル分け

ログに書き出される情報は以下の5つのレベルのどれかに分類されています。

:debug デバッグ情報 0
:info 一般的な情報 1
:warn 警告情報 2
:error エラー情報 3
:fatal 致命的なエラー情報 4


Railsが動作している時には、どのレベルの情報までログに書き出すかを決める”ログレベル”が設定されています。ログレベルが「:debug」であれば全ての情報がログに書かれ、「:warn」であれば:warn以上、つまり:warning, :error, :fatalの情報だけがログに書き出されます。

動作モードによってデフォルトのログレベルが決められています。

動作モード(RailsEnv) デフォルトのログレベル
development :debug
test :debug
production :info


configファイルでのログレベルの変更

各動作モードでのログレベルのデフォルトは上記のようになっていますが、それを変更したい場合、例えばproductionモードでエラー情報以上しか書き出さないようにしたい場合はconfig/environments/production.rb内で

config.log_level = :error

とします。

ログレベルを動的に変更する

特定のメソッド内だけでログレベルを:debugに変更したりといったことも可能です。

class UsersController < ApplicationController
  def index
    Rails.logger.level = Logger::DEBUG   # Rails.logger.level = 0 でもOK
  end
end


URLに与えるパラメータで制御することもできますね。ApplicationController内にbefore_filterでセットするとよいでしょう。

class ApplicationController < ActionController::Base
  before_filter :set_debug_mode

private
  def set_debug_mode
    if params[:debug]
      Rails.logger.level = Logger::DEBUG
    end
  end
end

こうするとURLのパラメータにdebugをつけた場合

http://localhost/users/2?debug

ログレベルが:debugになります。

productionモードなのにブラウザにRouting Errorが表示される

Ruby on Rails v3.2.11
Passenger v3.0.19

PassengerでデプロイしたRailsアプリで、RailsEnvをproductionとしているのに、URLに存在しないパスを指定した際に、通常表示されるはずの404エラーページではなく、なぜかdevelopmentモードでおなじみのRouting Errorページが表示されていました。

f:id:Oratok:20130205135922j:plain

このアプリ、もともとRails v3.1で作ったものをv3.2に更新したものなので、Railsのバージョンアップで何か変更があったのかもしれません。

で、いろいろ検索して、config/environments/production.rbファイルに下記の設定を追加したところ一応解決しました。

config.consider_all_requests_local = false


404エラーページがちゃんと表示されるようになりました (^^)

f:id:Oratok:20130205142509j:plain