プラグイン化と公開準備 の変更点
更新- 追加された行はこの色です。
- 削除された行はこの色です。
- ソフトウェア/rails/言語ネゴシエーション/プラグイン化と公開準備 へ行く。
- ソフトウェア/rails/言語ネゴシエーション/プラグイン化と公開準備 の差分を削除
SIZE(22){COLOR(RED){このページの内容は非常に古いです(Rails 1.x.x)。最新の Rails では洗練された国際化の機構が標準で入っているため、下記は読むだけ無駄な内容になっています。}}
[[ソフトウェア/rails/言語ネゴシエーション]]
#contents
* プラグイン化 [#j4d21556]
http://akimichi.homeunix.net/hiki/rails/?%A5%D7%A5%E9%A5%B0%A5%A4%A5%F3 ~
http://blog.s21g.com/articles/374 ~
あたりを参考に。
まずは script/generate でプラグインの雛型を作成し、
起動用スクリプト init.rb に require を追加。
LANG:console
$ script/generate plugin LanugageNegotiation
create vendor/plugins/lanugage_negotiation/lib
create vendor/plugins/lanugage_negotiation/tasks
create vendor/plugins/lanugage_negotiation/test
create vendor/plugins/lanugage_negotiation/README
create vendor/plugins/lanugage_negotiation/MIT-LICENSE
create vendor/plugins/lanugage_negotiation/Rakefile
create vendor/plugins/lanugage_negotiation/init.rb
create vendor/plugins/lanugage_negotiation/install.rb
create vendor/plugins/lanugage_negotiation/uninstall.rb
create vendor/plugins/lanugage_negotiation/lib/lanugage_negotiation.rb
create vendor/plugins/lanugage_negotiation/tasks/lanugage_negotiation_tasks.rake
create vendor/plugins/lanugage_negotiation/test/lanugage_negotiation_test.rb
create vendor/plugins/lanugage_negotiation/test/test_helper.rb
$ echo 'require "lanugage_negotiation"' >> vendor/plugins/lanugage_negotiation/init.rb
そして、config/environment.rb の内容をごそっと
vendor/plugins/lanugage_negotiation/lib/lanugage_negotiation.rb
へ移動。
動作チェック
LANG:console
$ wgetc http://localhost/negotiation/test
English: Test OK
$ wgetc http://localhost/negotiation/test.ja
日本語: テスト ばっちり
$ wgetc http://localhost/negotiation/test
日本語: テスト ばっちり
うーん、簡単すぎる。
恐らく今回の場合、以下のファイルは変更の必要は無い?
vendor/plugins/lanugage_negotiation/Rakefile
vendor/plugins/lanugage_negotiation/install.rb
vendor/plugins/lanugage_negotiation/uninstall.rb
vendor/plugins/lanugage_negotiation/tasks/lanugage_negotiation_tasks.rake
残るはドキュメントとテストケースだ。
* テストケース [#z90f29b8]
プラグインのテストケースを書かなければ。
テストケースとして書けるのかどうか自信はないのだけれど、
存在しない言語テンプレートがあった場合や、
言語に依存しないテンプレートがある場合にどうなるか、
ちゃんと試してみる必要がありそう。
** テストの動かし方 [#u8d040b6]
まずいきなりテストしてみる。
LANG:console
$ cd vendor/plugins/lanugage_negotiation/
$ rake
(in (rails)/negotiation/vendor/plugins/lanugage_negotiation)
/usr/bin/ruby1.8 -I"(rails)/negotiation/vendor/plugins/lanugage_negotiation/lib" \
-I"(rails)/negotiation/vendor/plugins/lanugage_negotiation/lib" \
-I"(rails)/negotiation/vendor/plugins/lanugage_negotiation/test" \
"/usr/lib/ruby/1.8/rake/rake_test_loader.rb" "test/lanugage_negotiation_test.rb"
/usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require': no such file to load -- active_support (LoadError)
from /usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `require'
from (rails)/negotiation/vendor/plugins/lanugage_negotiation/test/test_helper.rb:2
from ./test/lanugage_negotiation_test.rb:1:in `require'
from ./test/lanugage_negotiation_test.rb:1
from /usr/lib/ruby/1.8/rake/rake_test_loader.rb:5:in `load'
from /usr/lib/ruby/1.8/rake/rake_test_loader.rb:5
from /usr/lib/ruby/1.8/rake/rake_test_loader.rb:5:in `each'
from /usr/lib/ruby/1.8/rake/rake_test_loader.rb:5
rake aborted!
Command failed with status (1): [/usr/bin/ruby1.8 -I"(rails)/...]
(See full trace by running task with --trace)
$ cat test/test_helper.rb
require 'rubygems'
require 'active_support'
require 'active_support/test_case'
パスが通っていないらしい。
確かに通ってないんだけど、いくら何でも通ってなさすぎ。~
どうしてこんな事に?
ああ、この rake をいきなり起動しちゃだめなのね。
アプリケーションのホームから、
LANG:console
$ cd ../../..
$ pwd
(rails)/negotiation
$ rake test:plugins
(in (rails)/negotiation)
/usr/bin/ruby1.8 -I"(rails)/negotiation/lib" -I"(rails)/negotiation/test" \
"/usr/lib/ruby/1.8/rake/rake_test_loader.rb" "vendor/plugins/lanugage_negotiation/test/lanugage_negotiation_test.rb"
Loaded suite /usr/lib/ruby/1.8/rake/rake_test_loader
Started
.
Finished in 0.035457 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
うまく行ってる。
** プラグインのテストの作成 [#n11b0fa7]
あとは vendor/plugins/lanugage_negotiation/test/lanugage_negotiation_test.rb に
テストケースをもりもり書いていけばいい。
テストの書き方は~
LANG:ruby(linenumber)
test "what to test" do
assert true
assert_equal 1, 1
end
という感じ。
詳しい書式: http://www.ruby-lang.org/ja/man/html/Test_Unit.html
LANG:ruby(linenumber)
test "ActionController_AbstractRequest_acceptable_language?" do
ar = ActionController::AbstractRequest
ar.class_eval <<-EOS
def self.acceptable_languages=(v)
@acceptable_languages = v
end
EOS
ar.acceptable_languages= :ja, :en, :fr
assert ar.acceptable_language? :ja
assert ar.acceptable_language? :en
assert ar.acceptable_language? :fr
assert ! ar.acceptable_language?(:de)
assert ! ar.acceptable_language?(:es)
assert ar.acceptable_language? "fr"
assert ! ar.acceptable_language?("de")
end
問題なし。
LANG:ruby(linenumber)
test "ActionController_AbstractRequest_accepts_languages!" do
ar= ActionController::AbstractRequest
ar.class_eval <<-EOS
def self.acceptable_languages=(v)
@acceptable_languages = v
end
attr :cookies, true
attr :env, true
EOS
rec = ar.new
ar.acceptable_languages= :ja, :en, :fr
rec.cookies = {}
rec.env = {}
# server default
assert_equal [ :ja, :en, :fr ], rec.accepts_languages!
# ignore invalid specifications
rec.cookies = { 'rails_language' => ['xx'] }
rec.env = { 'HTTP_ACCEPT_LANGUAGE' => 'es, de' }
assert_equal [ :ja, :en, :fr ], rec.accepts_languages!
# Accept-Language
rec.env = { 'HTTP_ACCEPT_LANGUAGE' => 'en-us, fr, en, ja' }
assert_equal [ :en, :fr, :ja ], rec.accepts_languages!
rec.env = { 'HTTP_ACCEPT_LANGUAGE' => 'en-us;q=0.8, fr;q=0.1, en, ja' }
assert_equal [ :en, :fr, :ja ], rec.accepts_languages!
# cookie
rec.cookies = { 'rails_language' => ['ja'] }
assert_equal [ :ja, :en, :fr ], rec.accepts_languages!
rec.cookies = { 'rails_language' => ['fr'] }
assert_equal [ :fr, :en, :ja ], rec.accepts_languages!
rec.env = {}
assert_equal [ :fr, :ja, :en ], rec.accepts_languages!
# arg
assert_equal [ :ja, :fr, :en ], rec.accepts_languages!('ja')
assert_equal [ :en, :fr, :ja ], rec.accepts_languages!('en')
assert_equal [ :fr, :ja, :en ], rec.accepts_languages!('fr')
end
テストに引っかかった。
LANG:ruby
class ActionController::AbstractRequest.accepts_languages!(language=nil)
...
lsym= l.split(/;|\-/,2)[0].downcase.to_sym
の部分、
LANG:ruby
lsym= l.split(/;|\-/,2)[0].strip.downcase.to_sym
とする必要有り。
LANG:ruby(linenumber)
class MemoizedTestClass
extend ActiveSupport::Memoizable
def count
@count
end
def calc(v)
@count= ( @count || 0 ) + 1
v
end
memoize :calc
end
test "memoize" do
mt= MemoizedTestClass.new
mt.calc(1)
mt.calc(2)
mt.calc(1)
mt.calc(3)
mt.calc(1)
assert_equal 3, mt.count
mt.calc(1)
mt.calc(2)
mt.calc(3)
assert_equal 3, mt.count
mt.calc(4)
assert_equal 4, mt.count
assert MemoizedTestClass.memoized?(:calc)
MemoizedTestClass.unmemoize :calc
assert !MemoizedTestClass.memoized?(:calc)
assert_equal 4, mt.count
mt.calc(1)
mt.calc(2)
mt.calc(3)
mt.calc(4)
assert_equal 8, mt.count
MemoizedTestClass.memoize :calc
mt.calc(1)
mt.calc(2)
mt.calc(3)
assert_equal 8, mt.count
end
unmemoize 後にもう一度 memoize した場合には、
以前の計算結果が残ってしまうことに注意。
LANG:ruby(linenumber)
module ActiveSupport::Memoizable
def unmemoize(*symbols)
class_eval <<-EOS, __FILE__, __LINE__
#{memoized_ivar}= nil
などと書いてたけれど、呼び出されるコンテキストが
異なるので変数のクリアはできない。
この行は削除した。
LANG:console
$ rake test:plugins
/usr/bin/ruby1.8 -I"(rails)/negotiation/lib" -I"(rails)/negotiation/test" "/usr/lib/ruby/1.8/rake/rake_test_loader.rb" "vendor/plugins/lanugage_negotiation/test/lanugage_negotiation_test.rb"
Loaded suite /usr/lib/ruby/1.8/rake/rake_test_loader
Started
...
Finished in 0.038165 seconds.
3 tests, 25 assertions, 0 failures, 0 errors
テスト成功。
他にももう少し plugin test でちまちま書いてく事もできるんだけど、
controller の test と合わせていっきにやってしまった方が楽そう?
** negotiation アプリケーションのテストケース [#p80291d3]
コントローラ込みのファンクショナルテストについては、
プラグインのテストケースに入れるわけに行かないので、
negotiation アプリケーションの
test コントローラのテストケースとして書く事になる。
まずテストに必要なアクションとビューを追加。
LANG:console
$ jed app/controllers/test_controller.rb
class TestController < ApplicationController
caches_page :pagecached
caches_action :actioncached
def index
end
def pagecached
end
def actioncached
end
def fragmentcached
end
def render_action
render :action => :acceptid
end
def render_file
render :file => "test/acceptid.html"
end
def acceptid
end
end
$ echo "Test <%= render :partial=>'ok' %>" > app/views/test/index.html.en.erb
$ echo "テスト <%= render :partial=>'ok' %>" > app/views/test/index.html.ja.erb
$ echo "Page cached" > app/views/test/pagecached.html.en.erb
$ echo "ページキャッシュ" > app/views/test/pagecached.html.ja.erb
$ echo "Action cached" > app/views/test/actioncached.html.en.erb
$ echo "アクションキャッシュ" > app/views/test/actioncached.html.ja.erb
$ echo "<% cache :param1=>:test do %>Fragment cached <%= render :partial=>'ok' %><% end %>" > app/views/test/fragmentcached.html.en.erb
$ echo "<% cache do %>フラグメントキャッシュ <%= render :partial=>'ok' %><% end %>" > app/views/test/fragmentcached.html.ja.erb
$ echo "File" > app/views/test/renderfile_.html.en.erb
$ echo "ファイル" > app/views/test/renderfile_.html.ja.erb
$ echo "html <%= params[:id] %>" > app/views/test/acceptid.html.ja.erb
$ echo "text <%= params[:id] %>" > app/views/test/acceptid.text.ja.erb
$ echo "English <%= @content_for_layout %>" > app/views/layouts/application.text.en.erb
テストは test/functional/test_controller_test.rb に書き込む。
書き方は http://d.hatena.ne.jp/elm200/20070724/1185282738 が参考になる。
LANG:ruby(linenumber)
def setup
@ar = ActionController::AbstractRequest
@controller = TestController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
としておくと、
LANG:ruby(linenumber)
test "server default1" do
@ar.acceptable_languages = :ja, :en, :fr
@request.cookies = {}
@request.env = {}
get :index, :format=>"html"
assert_response :success
assert_template 'index.html.ja'
assert_equal "/test", @request.request_uri
end
のようなコードを書く事ができる。
キャッシュをテストするために、~
config/environments/test.rb
LANG:ruby(linenumber)
config.action_controller.perform_caching = true
さらに、
LANG:ruby(linenumber)
def assert_cached_page_exists( page )
full_path= ActionController::Base.__send__(:page_cache_path, page)
assert File.exist?(full_path), "Cached file '#{full_path}' not created."
end
def assert_cached_page( page )
ActionController::Base.class_eval <<-EOS
@@page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : ""
@@page_cache_directory += "/cahce_in_test_environment"
EOS
@controller.expire_page page
yield
assert_cached_page_exists page
end
として、development や production 環境にキャッシュを作成してしまわないようにする。
LANG:ruby(linenumber)
test "page cache 1" do
@ar.acceptable_languages = :fr, :ja, :en
@request.cookies = {}
@request.env = { }
page = "/test/pagecached.html.fr"
assert_cached_page( page ) do
get :pagecached, :format=>"html"
assert_response :success
assert_template 'pagecached.html.ja'
assert_equal "/test/pagecached", @request.request_uri
assert_layout "layouts/application.html.ja"
end
end
のようにテストする事ができる。
その他作ったヘルパー関数達
LANG:ruby(linenumber)
def assert_content_type(type)
flunk "Content-Type '#{type}' was expected but "+
"'#{@response.content_type}' found." unless
@response.content_type == type
end
def assert_cached_fragment_exists(key, message="Fragment cache ['#{key}'] not exists.")
flunk message unless @controller.fragment_exist? key
end
def assert_cached_fragment(key)
@controller.expire_fragment key
yield
assert_cached_fragment_exists key
end
def assert_layout(v)
assert_equal v, @response.template.send(
:_pick_template, @response.layout).path_without_extension
end
def log(s)
if s.is_a? String
Rails.logger.warn s
else
Rails.logger.warn s.inspect
end
end
テストはかなりたくさん書いたので、ここに全部は載せない。~
一部を取り出すと、
LANG:ruby(linenumber)
test "page cache 3" do
@ar.acceptable_languages = :fr, :ja, :en
@request.cookies = {}
@request.env = { 'HTTP_ACCEPT_LANGUAGE' => 'xx' }
page = "/test/pagecached.html.fr"
assert_cached_page( page ) do
get :pagecached, :format=>"html"
assert_response :success
assert_template 'pagecached.html.ja'
assert_equal "/test/pagecached", @request.request_uri
assert_layout "layouts/application.html.ja"
assert_content_type "text/html"
end
end
test "page cache 4" do
@ar.acceptable_languages = :fr, :ja, :en
@request.cookies = {}
@request.env = { 'HTTP_ACCEPT_LANGUAGE' => 'en' }
page = "/test/pagecached.html.en"
assert_cached_page( page ) do
get :pagecached, :format=>"html", :extra=>:param
assert_response :success
assert_template 'pagecached.html.en'
assert_equal "/test/pagecached?extra=param", @request.request_uri
assert_layout "layouts/application.html.en"
assert_content_type "text/html"
end
end
test "action cache 1" do
@ar.acceptable_languages = :fr, :ja, :en
@request.cookies = {}
key = "test.host/test/actioncached?rails_language=fr"
assert_cached_fragment( key ) do
get :actioncached, :format=>"html"
assert_response :success
assert_template 'actioncached.html.ja'
assert_equal "/test/actioncached", @request.request_uri
assert_layout "layouts/application.html.ja"
assert_content_type "text/html"
end
end
test "action cache 2" do
@ar.acceptable_languages = :fr, :ja, :en
@request.cookies = {}
key = "test.host/test/actioncached.ja"
assert_cached_fragment( key ) do
get :actioncached, :format=>"html", :rails_language=>"ja"
assert_response :success
assert_template 'actioncached.html.ja'
assert_equal "/test/actioncached.ja", @request.request_uri
assert_layout "layouts/application.html.ja"
assert_content_type "text/html"
end
end
こんな感じ。
実際にこれを走らせる。
LANG:console
$ rake test:functionals
rake aborted!
no such file to load -- (rails)/negotiation/db/schema.rb
(See full trace by running task with --trace)
$ rake db:schema:dump
$ rake test:functionals
/usr/bin/ruby1.8 -I"(rails)/negotiation/lib" -I"(rails)/negotiation/test" "/usr/lib/ruby/1.8/rake/rake_test_loader.rb" "test/functional/test_controller_test.rb"
Loaded suite /usr/lib/ruby/1.8/rake/rake_test_loader
Started
..........................
Finished in 0.358801 seconds.
26 tests, 96 assertions, 0 failures, 0 errors
実行は rake test:functionals で良い。~
schema.rb が無いと言われたので、rake db:schema:dump で作成した。
ミスして入力したテストで気づいた点。
LANG:ruby(linenumber)
module ActionView
class Template
def split(file)
elsif ActionController::AbstractRequest.acceptable_language?(m[3])
の部分などで、m[3] が nil になった場合、
LANG:ruby(linenumber)
class ActionController::AbstractRequest
def self.acceptable_language?(l)
acceptable_languages.include?(l.to_sym)
end
end
の l.to_sym でこける。
LANG:ruby(linenumber)
def self.acceptable_language?(l)
l.respond_to?(:to_sym) && acceptable_languages.include?(l.to_sym)
end
とすべき。
また、routes.rb では
LANG:ruby(linenumber)
map.connect ':controller/:action/:id', :format => 'html'
を始めの方に書いておかないと、
LANG:ruby(linenumber)
url_for(:controller=>:test, :action=>:acceptid, :index=>1, :format=>"html")
が
LANG:ruby(linenumber)
map.connect ':controller/:action/:id.:rails_language', :format => 'html',
:requirements => { :rails_language => lang_regexp }
を使って変換されてしまい、
/test/acceptid/1.
となるので、順番を入れ替える。
* fcgi 化 [#wd595311]
ちょっとオフトピックだけれど、はまったので。
fcgi 化するには基本的には RewriteRule で dispatch.cgi となっていたところを
dispatch.fcgi にするだけでよい。
LANG:console
$ jed public/.htaccess
RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
$ wgetc --header="Accept-Language: en" http://localhost/negotiation/test
エラー: ActionController::RoutingError (no route found to match to "/negotiation/test")
でも、なんでかうまくいかない。~
.htaccess の SetEnv RAILS_RELATIVE_URL_ROOT /negotiation の効果が得られていないみたい。
vendor/actionpack/CHANGELOG
*2.2 (November 21st, 2008)*
* AbstractRequest.relative_url_root is no longer automatically configured
by a HTTP header. It can now be set in your configuration environment with
config.action_controller.relative_url_root [Josh Peek]
どうやらこれらしい。~
なんで fcgi の時だけだめなんだろう?
changelog に書かれているのだからバグという事でもないのだろうし、
コードを追うのはやめにして、素直に environments/production.rb に
設定を追加したところ、動き出した。
さすが、cgi に比べると段違いに速い。
LANG:console
$ echo 'config.action_controller.relative_url_root = "/negotiation"' >> environments/production.rb
$ wgetc --header="Accept-Language: en" http://localhost/negotiation/test
日本語: テスト ばっちり
$ rm tmp/wget.cookie
$ wgetc --header="Accept-Language: en" http://localhost/negotiation/test
English: Test OK
$ wgetc --header="Accept-Language: en" http://localhost/negotiation/test.ja
日本語: テスト ばっちり
$ wgetc --header="Accept-Language: en" http://localhost/negotiation/test
日本語: テスト ばっちり
$ wgetc http://localhost/negotiation/test/pagecached
日本語: ページキャッシュ
$ ls public/test/*
public/test/pagecached.html.ja
$ wgetc http://localhost/negotiation/test/pagecached.en
English: Page cached
$ ls public/test/*
public/test/pagecached.html.en public/test/pagecached.html.ja
Counter: 5432 (from 2010/06/03),
today: 1,
yesterday: 1