Upload
shozo-hatta
View
579
Download
0
Embed Size (px)
Citation preview
Crafting Rails 4 Applications
第8章 Key-valueバックエンドを使用してアプリケーションを翻訳する
この章で学ぶこと• i18n(internationalization:国際化)のフレームワーク• Sinatra
• Railsのルーター• Devise
• Capybara
他にも盛りだくさん(i18nの話題は意外に少なめ)
YAMLを使用したローカライズ
言語コードをファイル名に持つYAMLファイルを準備する
I18n.lやi18n.tで呼び出すことで、ロケールに合うYAMLの文字列が使用される文字列をYAMLに集めるのは行儀も良い
おまけi18n-tasks gemを使用すると、YAMLロケールとビュー内i18nの照合などを行ってくれる
http://qiita.com/fakestarbaby/items/d9ad517fe674059041cd
本章の目標YAMLによるローカライズの特徴:
• Railsのデフォルト機能•複数のアプリケーションでyamlを共有しようとすると面倒• yaml更新後アプリの再起動が必要
本章ではKey-Valueストア(ここではRedis)をYAMLの代わりに使用してみるさらにSinatraを使用して訳語を動的に更新してみる
(本章ではプラグインではなくRailsアプリを作る)
8.1 Rails::Applicationの復習
(Railsのソースコード)
Rails::ApplicationクラスはRails::Engineを継承
これにより以下が行われる• Active Supportの読み込み• 読み込みパスの設定• ログ設定• アプリケーション自身のルーターとmiddlewaresスタックの使用 (7-
4, p149)
• すべてのプラグインの読み込み・初期化• コードとルートが更新されたときの再読み込み• タスクの生成とgenerate(必要に応じて)
3つのconfigファイル(第一章参照)
Application.initialize!で最終的にアプリケーションの初期化が行われる
初期化(p88)エンジンの初期化(railties)
初期化の例
参考コンソールで以下を実行するとイニシャライザを確認できる
Rails.application.initializers.map(&:name)
アプリ自身のイニシャライザの他にrailtiesとエンジンのイニシャライザもある
Rakefileapplicationファイルをrequireし、アプリケーションのrakeタスクを読み込んでいるデフォルトではenvironment.rbを読み込んでいないことに注意(速度をかせぐため)
rakeで:environmentを指定することでenvironment.rbを読み込む(rake db:migrateなどデータベースへのアクセスが必要な場合)
config.ruconfig.ruはRackの設定ファイルここではconfig/environment.rbをrequireしているさらにRailsアプリケーションをRackアプリケーションとして実行
Railsの設定ファイルはこれらの多くのファイルに分割されている
これらをhttp.confのごとく1つのファイルにまとめてみると初期化がよくわかる
1つにまとめた初期化
rackを起動空ディレクトリに前述のconfig.ruを置いてrackupを実行するとrack
が動作し、これだけでwebアプリケーションになる(localhost:9292
をブラウザで参照)
たった1ファイルでも立派なRailsアプリであり、通常のRailsはこれを拡張したものにすぎない
1ファイルRailsWebサーバーはSingleFile#call()を実行し、これによってRailsルーターが指し示すミドルウェアに渡される
Railsルーターはroot toパターンにマッチするリクエストをカスタムRackアプリケーションに渡す
ルーティング• draw()メソッドは、routes.rbが更新されるたびに既存のルーティングをすべてクリアしてエントリを再読み込みする
• さっき使ったroutes.appendとroutes.prependは一度読み込んだルーティングを常駐させる点が異なる
8.2 i18nのバックエンドと拡張
i18n.translate (エイリアス: i18n.t)
i18n.localize (エイリアス: i18n.l)
上記メソッドは、i18n.backend()に保存されたバックエンドに処理を委譲しているRailsではデフォルトで以下の3つのバックエンドが使用可能
i18n::Backend::Simple
デフォルト。YAMLを読み込んでメモリに保持i18n::Backend::KeyValue
任意のKey-Valueストアを使用可能i18n::Backend::Chain
複数のバックエンドを連鎖させることができる
i18nの機能config/environment/production.rbの以下のオプションは該当のロケールがない場合にフォールバックする
Rails外部でi18nを行っている場合フォールバックを以下のように変更できる
アクセントを除去する機能
※これらはバックエンドそのものではなくi18nの機能
i18nの機能• I18n::Backend::Cache: 翻訳結果にキャッシュストアを適用する(参照、式展開、
pluralize後)
• I18n::Backend::Cascade: カスケード。"foo.bar.baz"がない場合に"foo.bar"も探す• I18n::Backend::Fallbacks: 現在のロケールがない場合にデフォルトのロケールにフォールバックする
• I18n::Backend::Gettext: 国際化ライブラリgettextと.po ファイルをサポート• I18n::Backend::InterpolationCompiler: 式展開のキー (%{model}など) を訳文にコンパイルしてパフォーマンスを向上
• I18n::Backend::Memoize: 結果を参照。I18n::Backend::Cacheと異なりインメモリハッシュを使用する。Key-Valueバックエンドを使用する際に便利
• I18n::Backend::Metadata: メタデータを訳語に追加。複数形の個数や式展開の値など• I18n::Backend::Pluralization: “i18n.plural.rule"に基いて複数形/単数形をサポート• I18n::Backend::Transliterator: i18n.transliterate.ruleに基いてアクセント記号除去をサポート
今回はI18n::Backend::KeyValueとI18n::Backend::Memoizeのみを使用
以下のAPIを満たすKey-Valueバックエンドであればどんなものでもi18nのストアとして使用できる
• @store[] キーに対応する値を読み出す• @store[]= キーに対応する値を保存• @store.keys 保存されているキーをすべて読み出す
ここでは条件に合うストアとしてRedis gemを使用する
Redisサーバーをインストールして起動brew install rails
redis-server
RedisをGemfileに追加してbundle installを実行
コンソールを起動して動作を確認
lib/translator.rbを作成し、Redisインスタンスを設定するI18n::backend::Memoizeがポイント
config/application.rbでi18nフレームワークを設定
Key-ValueバックエンドはYAMLファイルの訳語の読み込みを各リクエストの前ではなく必要に応じて行なう
YAMLのすべての訳語をRedisストアに読み込んでおくには以下をターミナルで実行すればよい (YAML=>JSON変換も行われる)
(rails runnerはrubyファイルを実行するコマンド)
コンソールで確認:
8.3 Sinatraで書くここまで準備ができたところで、 Sinatraでアプリを書いてみる
Sinatraを使うと以下のように恐ろしくシンプルなWebアプリを書ける
ここでは一風変わった方法として、Sinatraを単独で使うのではなくRailsに統合する(というよりテスト/認証/セッションなどを拝借するという感じか)
テストから作成準備としてCapybaraを導入(p28)
テストロケールをポーランド語にすると日付表記がポーランド語になるか
Translator.reloadで既存のキーを捨てて訳語を再度読み込む
Translator::reload!を実装
Sinatraをビルドしてないのでこの時点ではテストは失敗
Sinatraを準備↓
app.rbにSinatra
アプリを実装→
ルーティングは/:from/:toの形式(fromは元のロケール、toは変更先ロケール)
Sinatraに明示的に転送している
ルーティングを設定
hamlテンプレートの評価はアプリケーションと同じコンテキストで行われていることに注意
hamlテンプレートapp.rbに書く
Sinatraのhamlテンプレートはアプリケーションと同じコンテキストで評価されていることに注意
Railsのテンプレートはビューのコンテキストで評価されるため、Railsはコントローラのインスタンス変数をビューで使用できるよう、背後でせっせとビューにコピーしている(p9)
ビューでのコントローラメソッドの呼び出しも明示的にコントローラ.メソッド()としなければいけない
hamlテンプレート内でlocale_valueが呼び出されている(キーと値を受け取ってRedisの値を返す)
このメソッドは標準のi18nハッシュも扱える
i18nのハッシュマッチングしやすくするため、{ "foo.bar" => "baz" }という訳のハッシュを保存すると、"foo.bar" は分解されて { "foo" => { "bar" =>
"baz"} } というハッシュも保存される
この場合、ハッシュの中には重複した訳文が含まれることになる
ここまでできたら、Sinatraをautoloadし、ルーティングをTranslatorに設定する
rails startしてhttp://localhost:3000/translation/en/plにアクセスすると
以下が表示されるがPOSTが未実装のためボタンを押すとエラー
テストも失敗する
テストを通すためにSinatraにpostルートを追加する1. i18nバックエンドに訳語を保持2. 訳語をJSONからRubyにデコード3. save()でRedisに保存し、ファイルシステムに出力4. 訳語を再表示する
※escape: falseとしているのは複合ハッシュを{ "foo\000.bar" =>
"baz" }のようにしないため
成功今度はStore translationボタンを押すと訳語が保存される
8.4 Deviseによるアプリケーション間認証とCapybara
おなじみDevise gemを使用して実装する
※DeviseはミドルウェアとしてWardenを使用する
bundle installでgemをインストール後、以下を実行してgenerateする
これでデフォルトのロケールファイルやイニシャライザも導入されるあとは若干追加設定を行なえばよい
Action Mailerdevelop環境にAction Mailerを導入(なぜ?)
アプリケーションビューにflashを追加
rootへのルーティングを追加
HomeコントローラでSinatraへのリンクをレンダリング
Deviseモデルを生成deviseでAdminモデルを生成
マイグレーション
(生成されたtest/fixtures/admins.ymlは不要なので削除してテストがパスするようにする)
認証画面rails sを実行して/admins/sign_in/にアクセス→
Deviseが提供するヘルパーメソッドを使用して、他のコントローラでも認証を導入できる(Adminモデルなのでauthentication_admin!)
SinatraでDeviseを使うには
DeviseにはSinatra用のヘルパーがないが、Wardenを使ってできる
コントローラでauthenticate_admin!()を実行すると実際には以下が実行される
env[“warden”]オブジェクトはWardenのプロキシであり、DeviseによってRails::Engine経由でRailsミドルウェアに追加される
このミドルウェアはルーターより前に実行されるので、Sinatraから使用できる
リクエストの流れ-WardenなどのミドルウェアはRailsルーターより前に実行される
- env[“warden”]プロキシはここに作成される- ルーターからコントローラとSinatraに振り分けられる- Sinatraからenv[“warden”].authenticate!を直接呼ぶ
before_filterしたいSinatraでも、Railsのコントローラと同じようにbefore_filterでDevise認証を追加できるだろうか…?
それも、Sinatraアプリケーション自体を変更せずに(変更できないサードパーティ製Sinatraアプリにも認証を追加したいので)
ルーターレベルの認証実は、ルーティングで認証を行なうことで、Sinatraアプリを変更せずに済む
ルーティングをこのように変更するとテストがfailするので認証が機能していることがわかる
DeviseをインストールするとRailsルーターでauthenticate()が使用できるようになる
authenticate()のDeviseソースコード↓
Capybaraによる認証操作テストを通すために結合テストにセットアップフックを追加する
これでテストはパスする
Capybaraによる認証操作Capybaraの認証操作は、ブラウザに対して直接入力している
CapybaraでセッションやCookiesをいじって認証を行なうことはできないそのわけは…
Capybara本書でCapybaraでテストするときにはActionController::IntegrationTestではなくActiveSupport::IntegrationCaseを使用していることに注意
ActiveController::IntegrationTestはセッションやcookiesにフルアクセスできるが、Capybara APIからはアクセス不可
⇒ActiveController::IntegrationTestでCapbaraは使えない
CapybaraCapybaraはそもそもエンドユーザーの立場での操作を再現する結合テストのためのもの
エンドユーザーはセッションのような実装を気にかけることはない
テストが実装に密着しすぎていると、たとえばセッションをcookiesに変更しただけでテストが失敗してしまう
⇒そういうわけでCapybaraはあえて実装を覆い隠すように作られた
CapybaraとドライバCapybaraは複数のドライバをサポートする
(Rack、Selenium、Poltergeistなど)
テストが実装に密着しているとドライバやブラウザを切り替えできない
ブラウザによってAPIの範囲が違うので、Capybaraは最大公約数的にサポートする
CapybaraとドライバCapybaraのデフォルトドライバはRack
⇒パフォーマンスは高いが機能が少ない(JavaScriptをテストできないなど)
ドライバをSeleniumやPoltergeistなどに切り替えることでJavaScriptをテストできるtest_helpers.rbの記述例↓
CapybaraとドライバSeleniumはデフォルトでFirefoxをブラウザとして使用する
Capybara+Seleniumでテストを実行するとFirefoxが起動するのですぐわかる
Transactional FixturesCapybaraを使用するときはtransactional fixturesをオフにすること
理由:
- Capybaraはスレッドごとに新しいブラウザインスタンスを起動する(実際の動かしてみるとわかりやすい)
- データベーストランザクションのテストでtransaction fixturesをオンにしても、インスタンス同士が関連していないのでうまくいかない(トランザクションデータはコミットするまで共有できない)
- transactional fixturesを使うとテスト間でデータベースがクリアされない(Database Cleaner gemなどを使えばできる)
8.5 まとめ• Railsの構造を改めて理解• Rackのおかげで異なるフレームワーク(RailsやSinatra)が共存できる
• Deviseかわいいよ• Capybaraかわいいよ• Railsを構成する多くの要素と連携を熟知することで、Railsアプリケーションの開発に役立てよう• 自分のコードをスリム&DRYにできる• 他人のコードを読む時に役立つ• デバッグに役立つ
お疲れさまでした!