Rails3 で JSON 出力時に、日本語が文字参照となってしまうのを防ぐ方法
JSON 出力時に、日本語が文字参照となってしまうのは、
activesupport-3.0.9/lib/active_support/json/encoding.rb の
173 行目にある def encode_json(encoder) のためです。
class String def as_json(options = nil) self end #:nodoc: def encode_json(encoder) encoder.escape(self) end #:nodoc: end
これを、下記のように application.rb 等でオーバーライドしてあげれば
文字参照を防いで出力することができるようです。
class String def encode_json(encoder) '"' + self + '"' end end
Rails3 で Ajax (will_paginate 2.3.15 対応)
以前、http://d.hatena.ne.jp/nedate/20101006/1286341476 にて Rails3 で link_to_remote + will_paginate に対応する方法を書きましたが、will_paginate のバージョンが上がったらこの対応では正常に動作しなくなりました orz
というわけで、will_paginate 2.3.15 にて link_to_remote に対応する方法です。
まずは、app/helpers/remote_link_renderer.rb に下記のような内容を記述します。
class RemoteLinkRenderer < WillPaginate::LinkRenderer def page_link(page, text, attributes = {}) params = @options[:params][:url] params[:page] = page @template.link_to_remote( text, { :url => params, :update => @options[:params][:update], :loading => @options[:params][:loading] } ) end end
そして will_paginate を下記のように書いてあげれば動くようになると思います。
<%= will_paginate( @users, :renderer => RemoteLinkRenderer, :params => { :url => {:action => 'show'}, }, :update => 'detail_zone', :loading => "showLoading('small', 'detail_zone')" ) -%>
Rails3 で RDoc ドキュメント生成時に invalid byte sequence in UTF-8 エラーが出た場合の対応
日本語でコメントを記述したプログラムを利用して RDoc ドキュメントを生成するときに、
下記のようなエラーが出て途中で止まってしまうことがあります。
$ rdoc -c UTF-8 -U Parsing sources... Before reporting this, could you check that the file you're documenting compiles cleanly--RDoc is not a full Ruby parser, and gets confused easily if fed invalid programs. The internal error was: (ArgumentError) invalid byte sequence in UTF-8 uh-oh! RDoc had a problem: invalid byte sequence in UTF-8 run with --debug for full backtrace
これは、ドキュメントを生成する際に RDoc の内部で 1024byte 毎にファイルを区切っていて、
1024byte の部分がちょうど日本語 UTF-8 の 3byte の中になってしまった場合にエラーになるようです。
実験として、下記のような model を作成してみました。
# coding: utf-8 # #= エラー再現確認用モデル # # 1024byte の部分が日本語だった場合の実験用モデル # class Mail < ActiveRecord::Base # # 1024byte の部分が、日本語 UTF8 の 1 文字の途中だった場合は # rdoc -c UTF-8 -U を実行した際にエラーとなって rdoc が生成できないみたい。 # この実験のために日本語をたくさん書いてエラーを再現してみようと思います。 # ここまで書いてまだ半分の 512byte まで到達していなくてちょっとびっくり。 # 意味の無い文書を長く書くことも結構難しいですね。 # ここで色々なことを書いておけば SEO でヒットするキーワードが多くなるかなとか # 適当なことを考えながら淡々と文章を書き続けています。 # # あとちょっとで 1024byte にまで届きそう! # がんばれ俺!負けるな俺! # この行がちょうど 1024byte になるのでここで打ち止め! end
そして /usr/local/lib/ruby/gems/1.9.1/gems/rdoc-2.5.11/lib/rdoc/parser.rb の
def self.binary? に下記のような修正を加えてみます。
def self.binary?(file) s = File.read(file, 1024) or return false set_encoding(s) p s # この行を状況確認用に追加 if s[0, 2] == Marshal.dump('')[0, 2] then true elsif file =~ /erb\.rb$/ then false elsif s.scan(/<%|%>/).length >= 4 || s.index("\x00") then true elsif 0.respond_to? :fdiv then s.count("^ -~\t\r\n").fdiv(s.size) > 0.3 else # HACK 1.8.6 (s.count("^ -~\t\r\n").to_f / s.size) > 0.3 end end
そして rdoc -c UTF-8 -U を実行すると下記のようなエラーで止まります。
(読みやすいように改行を加えています)
"# coding: utf-8\n#\n#= エラー再現確認用モデル\n#\n# 1024byte の部分が日本語だった場合の実験用モデル \n#\n\n\nclass Mail < ActiveRecord::Base\n #\n # 1024byte の部分が、日本語 UTF8 の 1 文字の途中だっ た場合は\n # rdoc -c UTF-8 -U を実行した際にエラーとなって rdoc が生成できないみたい。\n # この実験 のために日本語をたくさん書いてエラーを再現してみようと思います。\n # ここまで書いてまだ半分の 512byte まで到達していなくてちょっとびっくり。\n # 意味の無い文書を長く書くことも結構難しいですね 。\n # ここで色々なことを書いておけば SEO でヒットするキーワードが多くなるかなとか\n # 適当なことを考えながら 淡々と文章を 書き続けています。\n #\n # あとちょっとで 1024byte にまで届きそう!\n # がんばれ俺! 負けるな俺!\n # この行がちょうど 1024byte になるのでここで打ち止\xE3" Before reporting this, could you check that the file you're documenting compiles cleanly--RDoc is not a full Ruby parser, and gets confused easily if fed invalid programs. The internal error was: (ArgumentError) invalid byte sequence in UTF-8 uh-oh! RDoc had a problem: invalid byte sequence in UTF-8 run with --debug for full backtrace
rdoc 生成時の app/models/mail.rb のファイルサイズは 1034byte です。
なので utf-8 を 1 文字 3byteで最後から逆算していくと、
下記のように "打ち止め!" の "め" の部分がちょうど 1024byte 目から始まってしまっています。
byte | char ---------------- 1015〜1017 | 打 1018〜1020 | ち 1021〜1023 | 止 1024〜1026 | め 1027〜1029 | ! 1030 | \n 1031 | e 1032 | n 1033 | d 1034 | \n
というわけで、こういったケースはプログラム内の先頭や途中に適当に半角文字を追加して、
日本語が 1024byte にぶつからないように微調整してあげることでエラーが出ないようになります。
あぁ、日本語ってめんどくさい、、、
Rails3 で Session を Memcache に入れる
Rails3 で Session を Memcache に入れるための手順です。
まずは、memcache-client をインストールするために Gemfile に下記の行を追加します。
gem 'memcache-client'
次に bundle install を実行して memcache-client をインストールします。
$ sudo bundle install Fetching source index for http://rubygems.org/ : Installing memcache-client (1.8.5) : Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
デフォルトでは Session は Cookie に保存するようになっているので、config/initializers/session_store.rb を下記のように修正して Memcache に Session を保存するように修正します。
(memcache_server にて指定している Memcache サーバは、開発環境・本番環境と切り替えたほうが良いと思いますので、config/environments/development.rb 等に適宜逃がしてください。)
Project::Application.config.session_store :mem_cache_store Project::Application.config.session_options = { :cookie_only => false, :key => '_session', :memcache_server => 'localhost:11211' }
これで Session は Memcache に保存されるようになります。
ですが、Session ID はまだ Cookie 上に保存されます。
これを URL パラメータとして引き回す場合は jpmobile を使用します。
これも memcache-client と同様に Gemfile に jpmobile の設定を追加して bundle install を実行します。
gem 'memcache-client' gem 'jpmobile'
$ sudo bundle install Fetching source index for http://rubygems.org/ : Using memcache-client (1.8.5) Installing jpmobile (0.1.2) : Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
あとは app/controllers/application_controller.rb に trans_sid :always を追記すれば Cookie を使用しないで Session を Memcache に保存できます。
class ApplicationController < ActionController::Base trans_sid :always protect_from_forgery end
Rails3 で Ajax (link_to_remote + will_paginate 対応)
Rails3 で Ajax 対応をする場合は下記のような感じで view を書いて、リクエストを受け取る側の Controller 等でも JavaScript を書かないと動かないようです。
<%= link_to('参照', {:action => 'show'}, :remote => true) %>
可能であればこれを勉強して Rails3 っぽく書きたいところなのですが、とりあえず Rails 2.x の link_to_remote に逃げる方法です。
(Rails3 の Ajax + RJS は後で勉強するつもりです(^^;)
まずは、prototype_legacy_helper を vendor/plugins に配置します。
$ cd vendor/plugins $ git clone git://github.com/rails/prototype_legacy_helper.git
これで view 上で link_to_remote が動作するようになるので、下記のような感じで記述できます。
<%= link_to_remote( '参照', { :url => {:action => 'show'}, :update => 'update_target', :loading => "showLoading('big', 'update_target')", } ) -%> <div id="update_target">ここを書き換えたい</div>
サーバからのレスポンスを待っている間は Alax っぽくロード中の画像を出したいので、:loading で showLoading を呼び出しています。
これは、下記の JavaScript を public/javascripts/application.js 等に書いておきます。
/* * ロード画面を出力する */ function showLoading(load_image_size, load_target) { // 画像オブジェクトの作成 img_element = document.createElement('img'); img_element.src = load_image_size == 'big' ? '/images/ajax-loader.gif' : '/images/ajax-loader-small.gif'; img_element.align = 'center'; // センタリングオブジェクト center_element = document.createElement('center'); center_element.appendChild(img_element); // 画像を表示するメソッド var output_loading = function(load_image, target_place) { target = document.getElementById(target_place); while (target.hasChildNodes()) { target.removeChild(target.firstChild); } target.appendChild(load_image); } // ロード画像を表示する場所が複数か否かの分岐 if (load_target instanceof Array) { for (var i = 0; load_target[i]; i ++) { output_loading(center_element, load_target[i]); } } else { output_loading(center_element, load_target); } }
/images/ajax-loader.gif や /images/ajax-loader-small.gif などの画像は Ajaxload - Ajax loading gif generator で自分好みの画像を作成すると良い感じになります。
これで link_to_remote での Ajax リクエストが実行できるようになります。
FORM から Ajax リクエストを実行したい場合は form_remote_tag を使用すれば、同様の動作を行うことが可能です。
ただし、prototype_legacy_helper は will_paginate に対応していないので別途対応が必要になります。
まず、app/helpers/remote_link_renderer.rb に下記のような内容を記述します。
(こちらは、id:donghai821 さんの記事を参考にさせていただきました)
class RemoteLinkRenderer < WillPaginate::LinkRenderer private def link(text, target, attributes = {}) if target.is_a? Fixnum page = target target = url(target) target[:url][:page] = page end @template.link_to_remote(text, target, attributes) end def url(page) @base_url_params ||= begin url_params = base_url_params merge_optional_params(url_params) url_params end url_params = @base_url_params.dup add_current_page_param(url_params, page) return url_params end end
次に will_paginate を下記のように記述すると、ページ送りも Ajax で動作するようになります。
<%= will_paginate( @users, :renderer => RemoteLinkRenderer, :params => { :url => {:action => 'show'}, :update => 'update_target', :loading => "showLoading('big', 'update_target')", }, :previous_label => '<- 前', :next_label => '次 ->' ) -%>
Rails3 で ActiveRecord にて取得した UTF-8 の日本語を view に表示する
ActiveRecord にて取得した UTF-8 の日本語の情報を view で表示する方法です。
たとえば、Controller を下記のように記述します。
# coding: utf-8 class UserController < ApplicationController def index User.create({ :name => 'てすと', }) @user = User.find(:first) end end
そして、view を下記のように記述します。
<h1>日本語表示のテスト</h1> <%= @user.name %>
すると、下記のようなエラーが出て日本語を表示することが出来ません。
incompatible character encodings: UTF-8 and ASCII-8BI
これは、日本語を ActiveRecord 上では ASCII-8BI で扱い view 上では UTF-8 で扱っているため、違う文字コードの日本語を連結することは不可能であるというエラーのようです。
そのため view に日本語を一切書かなければ正常に表示することが出来ますが、そういうわけにもいきません。
単純に回避するのであれば、下記のように view 上で日本語を UTF-8 に強制的に encode してあげればエラーは出ません。
<h1>日本語表示のテスト</h1> <%= @user.name.force_encoding('UTF-8') %>
ですが、全てのデータ出力に force_encoding を付けていくのは DRY ではありません。
そこで app/helpers/application_helper.rb に下記のように記述すると force_encoding をしなくても日本語を表示できるようになります。
module ActionView class OutputBuffer < ActiveSupport::SafeBuffer def <<(value) super(value.to_s.force_encoding('UTF-8')) end alias :append= :<< end end
■お詫び
ActionView のオーバーライドは最初は config/environment.rb に記述してくださいと書いていましたが、設定を読み込む順番によってはオーバーライドされない現象が確認できました。
こちらは、app/helpers/application_helper.rb に記述することで動作することが確認できましたので、修正させていただきました。
Rails3 で RSpec + RCov 日本語対応
Rails3 でプロジェクトを作成し、RSpec と RCov を日本語で動作させるまでの手順です。
rails (3.0.0) や rspec (2.0.0.beta.22) 等の必要な gem は既にインストールされているものとします。
まずは、普通に Rails プロジェクトを作成して、config/database.yml を開発環境に合わせて設定しておきます。
$ rails new project create create README create Rakefile create config.ru create .gitignore create Gemfile : :
プロジェクト内にて RSpec と RCov を使用するために Gemfile に下記を追記します。
gem "rcov" group :test do gem 'rspec-rails', '>= 2.0.0.beta', :group => :development end
プロジェクトのテストフレームワークを RSpec にするために
config/application.rb に下記のような修正を加えます。
module Project class Application < Rails::Application config.generators do |g| g.test_framework :rspec end end end
RSpec テスト用の設定ファイルを設置します。
$ ./script/rails generate rspec:install create .rspec create spec create spec/spec_helper.rb create autotest create autotest/discover.rb
ここまで基本的な準備は完了です。
controller などを作成すると、同時に rspec 用のテストファイルが生成されます。
$ ./script/rails generate controller user index
create app/controllers/user_controller.rb
route get "user/index"
invoke erb
create app/views/user
create app/views/user/index.html.erb
invoke rspec
invoke helper
create app/helpers/user_helper.rb
invoke rspec
create spec/helpers/user_helper_spec.rb
これで RSpec を実行する環境はできましたので、下記のようにテストを書いてみます。
1 行目の "# coding: utf-8" を記述しておくことで、テスト内容を日本語 UTF-8 で記述して実行することが可能になります。
# coding: utf-8 require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') describe UserController do describe 'index method' do it 'GET "index" リクエストが成功すること' do get 'index' response.should be_success end end end
次に RSpec で記述したテストのカバレッジを確認するために RCov を実行したいのですが、
普通に rake spec:rcov を実行すると、下記のようなエラーが出て実行できません。
これは、RSpec テスト内の日本語 UTF-8 が問題で文字を正常に認識できていないために発生する問題のようです。
$ rake spec:rcov (in /Users/nedate/svn/project) /usr/local/bin/ruby -S bundle exec rcov --exclude /gems/,/Library/,/usr/,lib/tasks,.bundle,config,/lib/rspec/,/lib/rspec- "./spec/controller/user_spec.rb" ** WARNING: Ruby 1.9 Support is experimental at best. Don't expect correct results! ** /usr/local/lib/ruby/gems/1.9.1/gems/rcov-0.9.9/lib/rcov/file_statistics.rb:115:in `block in is_code?': invalid byte sequence in US-ASCII (ArgumentError) from /usr/local/lib/ruby/gems/1.9.1/gems/rcov-0.9.9/lib/rcov/file_statistics.rb:112:in `each' from /usr/local/lib/ruby/gems/1.9.1/gems/rcov-0.9.9/lib/rcov/file_statistics.rb:112:in `each_with_index' from /usr/local/lib/ruby/gems/1.9.1/gems/rcov-0.9.9/lib/rcov/file_statistics.rb:112:in `is_code?' from /usr/local/lib/ruby/gems/1.9.1/gems/rcov-0.9.9/lib/rcov/file_statistics.rb:234:in `extend_heredocs' from /usr/local/lib/ruby/gems/1.9.1/gems/rcov-0.9.9/lib/rcov/file_statistics.rb:40:in `initialize' from /usr/local/lib/ruby/gems/1.9.1/gems/rcov-0.9.9/lib/rcov/formatters/base_formatter.rb:50:in `new' from /usr/local/lib/ruby/gems/1.9.1/gems/rcov-0.9.9/lib/rcov/formatters/base_formatter.rb:50:in `add_file' from /usr/local/lib/ruby/gems/1.9.1/gems/rcov-0.9.9/lib/rcov/code_coverage_analyzer.rb:143:in `block (2 levels) in dump_coverage_info' from /usr/local/lib/ruby/gems/1.9.1/gems/rcov-0.9.9/lib/rcov/code_coverage_analyzer.rb:142:in `each' from /usr/local/lib/ruby/gems/1.9.1/gems/rcov-0.9.9/lib/rcov/code_coverage_analyzer.rb:142:in `block in dump_coverage_info' from /usr/local/lib/ruby/gems/1.9.1/gems/rcov-0.9.9/lib/rcov/code_coverage_analyzer.rb:135:in `each' from /usr/local/lib/ruby/gems/1.9.1/gems/rcov-0.9.9/lib/rcov/code_coverage_analyzer.rb:135:in `dump_coverage_info' from /usr/local/lib/ruby/gems/1.9.1/gems/rcov-0.9.9/bin/rcov:433:in `block in <top (required)>' rake aborted! Command failed with status (1): [/usr/local/bin/ruby -S bundle exec rcov --...] (See full trace by running task with --trace)
これは、spec/spec_helper.rb に下記のように記述することで回避できます。
# # quick monkey patch for rcov # # http://codefluency.com/post/1023734493/a-bandaid-for-rcov-on-ruby-1-9 # if defined?(Rcov) class Rcov::CodeCoverageAnalyzer def update_script_lines__ if '1.9'.respond_to?(:force_encoding) SCRIPT_LINES__.each do |k,v| v.each { |src| src.force_encoding('utf-8') } end end @script_lines__ = @script_lines__.merge(SCRIPT_LINES__) end end end