Categories
open all | close allTags
フォーム | JustPosted | ドキュメント | パソコン | デュアル・コア | Subversion | Migration | rake | 名称 | テスト | スキンエンジン | rubygems | タグ | カテゴリ | 国際化 | RESTful | Flash | Aptana | CSRF | 認証Search
RESTでRest
Rails2.0についての開発者のブログを読んでいたら,RESTについて強調しているので,あらためて勉強,っていうか知らなかったですよ,REST。今のところGETを使う閲覧系しか実装していないから1.2の上でもRESTにはできるはず。将来を考えたらやっぱりそうするべきなんでしょうねえ,と思ってちょっとそちらを調べています。ということで今日もコードはなし。ちょっと休憩(rest)。
日本語の資料ではここが一番役に立つのかな。これの原文の英語版はこちら(PDF)。
RESTなアーキテクチャにするにはクッキーにユーザー情報を格納しておくというのはまずいため,のびのびになっている認証にもまた絡んできます。BASIC認証は使っていいよという話もありますが,ブログの場合はログインしていないユーザーにも閲覧できるようにする必要があるためBASIC認証とはちょっと相性がよくなさそう。それともログインが必要なURLにはユーザーIDを入れてしまうとか? なんだかかえって使い勝手を落としてしまいそうな気もします。
使うのをあきらめたacts_as_authenticatedの派生プラグインでrestful_authenticationというのもあるようですが。どうしましょうかねえ。
acts_as_authenticatedを統合するのは難しい
いろいろ調べた結果(ここが一番参考になりました),acts_as_authenticatedを認証機能として組み込もうとしたのですが,かなり面倒です。既存のNucleusからの移行性をどこまで持たせるかという問題があり,member情報をそのまま引き継ごうとすると,(当たり前ですが)acts_as_authenticatedでは認証できません。
後々の手間を考えると,認証はなるべく軽く,最終的にはここもプラガブルにしたいのですが,今のNucleusの認証を移植する方が結局手間が少なくてすみそうです。
一番いいのは当面Nucleusの認証で運用しておき,ユーザーに自分でバージョン・アップさせて別の認証に置き換えるといった形ですが,ちょっと自分の手には余る感じです。
認証に寄り道をしようかと思う
コメント更新を実装するつもりだったのですが,セッション管理とかしたほうがいいのかなあなどと考えつつ,それだったら先に認証を作っておかないといけないのかなあと,寄り道というか当初はその予定だったのですが,認証系を調べ始めています。プラグインがたくさんで理解するのが大変。acts_as_authenticatedというのがかなり使われているっぽいのですが,もう1つ機能がよく分かっていません。before_filterでチェックするのでしょうけど,ログインを強制されるようだと困ってしまいます。ブログの場合は「名無し」のユーザーもいるわけだから。そういった使い方ができるのかどうかがまだ見えていません(要は何もわかっていない)。とりあえずプラグインのインストールだけしてしまいましたが,後はどうしようって感じです。
フォームをフォームヘルパを使う形に改変
Railsではフォームヘルパという機能で,フォームとモデルとの連携を簡単にできるというのでフォームを改変しました。
<%= form_tag(:controller=>'actions', :action=>'comment_submit', :id=>itemid) %>
<div class="commentform">
<%= errordiv %>
<label for="nucleus_cf_body"><%= text("_COMMENTFORM_COMMENT") %></label>
<%= text_area :comment, :cbody, "cols" => 40, "rows" => 10 %>
<label for="nucleus_cf_name"><%= text("_COMMENTFORM_NAME") %></label>
<%= text_field :comment, :cname, "size"=>40 %>
<label for="nucleus_cf_mail"><%= text("_COMMENTFORM_MAIL") %></label>
<%= text_field :comment, :cmail, "size"=>40, "maxlength"=>80 %>
<label for="nucleus_cf_email"><%= text("_COMMENTFORM_EMAIL") %></label>
<%= text_field :comment, :cemail, "size"=>40, "maxlength"=>60 %>
<%= callback(:FormExtra,:commentform_notloggedin) %>
<%= check_box_tag("remember", value = "1", checked = false) %>
<label for="nucleus_cf_remember"><%= text("_COMMENTFORM_REMEMBER") %></label>
<%= submit_tag(text("_COMMENTFORM_SUBMIT")) %>
</div>
<%= end_form_tag %>
表示内容は変わりませんがタグは少し変わります。次にようやくコメントの処理を書く予定。今週は忙しくてほとんど作業できていません。
フォームと言語の処理
フォームはRailsのビューの機能を使ってパーシャル・レンダリングで実装します。具体的にはcommentform-notloggedin.templateだったら,これをviewsの下のformsディレクトリに置き,「_commentform_notloggedin.rhtml」と,①最初に_を付ける,②ハイフンを_にする,③拡張子をrhtmlにするといったファイル名の変更をします。中身もビューの仕様に合わせて修正します。
<a id="nucleus_cf"></a>
<form method="post" action="#nucleus_cf">
<div class="commentform">
<input type="hidden" name="action" value="addcomment" />
<input type="hidden" name="url" value="<%= formdata(:destinationurl) %>" />
<input type="hidden" name="itemid" value="<%= itemid %>" />
<%= errordiv %>
<label for="nucleus_cf_body"><%= text("_COMMENTFORM_COMMENT") %></label>
<textarea name="body" class="formfield" cols="40" rows="10" id="nucleus_cf_body"><%= formdata(:body) %></textarea>
<label for="nucleus_cf_name"><%= text("_COMMENTFORM_NAME") %></label>
<input name="user" size="40" maxlength="40" value="<%= formdata(:user) %>" class="formfield" id="nucleus_cf_name" />
<label for="nucleus_cf_mail"><%= text("_COMMENTFORM_MAIL") %></label>
<input name="userid" size="40" maxlength="60" value="<%= formdata(:userid) %>" class="formfield" id="nucleus_cf_mail" />
<label for="nucleus_cf_email"><%= text("_COMMENTFORM_EMAIL") %></label>
<input name="email" size="40" maxlength="100" value="<%= formdata(:email) %>" class="formfield" id="nucleus_cf_email" />
<%= callback(:FormExtra,:commentform_notloggedin) %>
<input type="checkbox" value="1" name="remember" id="nucleus_cf_remember" <%= formdata(:rememberchecked) %> />
<label for="nucleus_cf_remember"><%= text("_COMMENTFORM_REMEMBER") %></label>
<input type="submit" alt="<%= text("_COMMENTFORM_SUBMIT") %>" value="<%= text("_COMMENTFORM_SUBMIT") %>" class="formbutton" />
</div>
</form>
で,formdataやtextをアプリケーションのヘルパで実装します。ここではtextのところだけ示します。
def text sym
l(sym.to_sym)
end
上の例ではtextの引数は文字列にしていますが,シンボルでも構いません。
次に多言語化の処理です。
Simple Localizationは,YAML形式の言語ファイルを作ることで多国語化を行うので,現在のNucleusのスタイルと非常によく似ています。さらにYAMLの階層構造を利用できるので,ネームスペースをあまり気にしなくていいというメリットがあります。今回の実装では,Nucleusの言語ファイルからなるべく簡単に移植できるように,次のようにしています。
libの下にlanguagesディレクトリを置き,そこに言語ファイルを置いていきます。今のところja.ymlだけが存在しています。中身はこんな感じ。
app:
_ERROR_ITEMCLOSED: このアイテムは閉じました
_COMMENTFORM_COMMENT: コメント
_COMMENTFORM_NAME: お名前
_COMMENTFORM_MAIL: ウェブサイト
_COMMENTFORM_EMAIL: メール
_COMMENTFORM_REMEMBER: 情報を記憶しておく
_COMMENTFORM_SUBMIT: コメントを追加
本当は階層構造にしていった方がきれいですが,移植のしやすさを考えてフラットにしてしまいました。
そして,environment.rbに次のように書きます。
simple_localization :language => [:ja, :en]
ArkanisDevelopment::SimpleLocalization::Language.lang_file_dirs << "#{File.dirname(__FILE__)}/../lib/languages"
最初の行は言語ファイルの種類を示しています。1番目が優先的に使われます。2行目がこのアプリ用の言語ファイルを読ませるための設定です。これで,先ほどのYAMLを読み込んで表示するようになりました。
国際化プラグイン
Simple Localizationは,プラグインにも利用できることがわかったので,これを採用することにしました。あまり進んでいませんが
render :partialでフォームを貼り込めることは確認できました。国際化について補足
プラグインを国際化するのにも使えるかどうか。メールはどうするかっていうのもあるけど,そこまで国際化ツールに期待すると機能が肥大化してしまいそう。
さて,フォームをどうしようか
前に,認証,更新系の順に作業する予定と書きましたが,先に更新系をやろうかと思っています。その前段階としてフォームをどうするかで悩み中。
まず,現状のNucleusはNucleusのパーサーを使ってフォームを生成しています。これをどうするか。
今のRails版のアプローチでは,そのまま移植するのが一番楽ですが,フォーム部分はスキンと独立しているので,Nucleusの<%,%>を使う必然性はありません。なので,この部分はRailsのビュー機能を使い,Partial Renderingで入れていくのがいいのかなと思っています(それで動くかどうかは未テスト)。
それともう1つ,国際化をどうするかという問題があります。RailsのWikiの国際化方法の比較ページを見ていたのですが,決めかねています。世間的にはgettextの評価が高いのかと思っていますが,gemでインストールしなければいけないのがちょっと面倒なのと,「gettextでなくちゃ」というのがどこなのか,よく分かっていないというのがあります。
Railsのプラグインとして使える中では,DBに言語データを入れていくGlobalizeは面倒なので却下。なんとなくよさげかなあと思うのがGloc。ただ,Simple Localizationというのも導入のしきいが低そうなところは好感が持てます。とりあえずSimple Localizationを入れておいて,後から変えることを検討するというのが妥当なところかもしれません。
テンプレートのコメントの処理がよく分からない
Nucleusのテンプレートにおけるコメントの処理がよく理解できなくて,先に進めないでいます。なんだかややこしいことをやっているのは分かるのですが。えーと,これだけでは何なので,今まで書いていなかった,基本的な処理の流れを紹介(といっても大したことはありませんが)。
まず,入ってきたURLを振り分けるのはroutes.rbの役割。ここはこんな感じです(デフォルトブログの場合)。
# rules for the index page
map.blog '', :controller => "application", :action => 'index'
map.blogcategory 'catid/:catid', :controller => "application", :action => 'index'
# rules for the item page
map.item 'item/:itemid', :controller => 'application', :action => 'item'
map.itemcategory 'item/:itemid/catid/:catid', :controller => 'application', :action => 'item'
# rules for the archivelist page
map.archivelist 'archivelist', :controller => 'application', :action => 'archivelist'
# rules for the member page
map.member 'member/:memberid', :controller => 'application', :action => 'member'
# rules for the archive page
map.archive 'archive/:daymonth', :controller=> 'application', :action => 'archive', \
:requirements=>{:daymonth => /\d{4}-d{1,2}(-d{1,2})?/}
map.archivecategory 'archive/:daymonth/catid/:catid', :controller=> 'application', \
:action => 'archive', \
:requirements=>{:daymonth => /\d{4}-d{1,2}(-d{1,2})?/}
名前付きのルーティングもできるように,map.connectではなくて,各ルートに名前を付けています。
これを見て分かるように,このあたりは全部applicationというメインのコントローラが処理します。actionのところでスキンタイプごとに振り分ける形。
このほか,ポスト系を処理するactionのコントローラと管理用のadminコントローラ,rss用のコントローラを別途作っていく予定です。
これを受けるapplication.rbは今のところこんな感じ。グローバル変数を多用しているところがちょっと格好悪いのですが。
class ApplicationController < ActionController::Base
private
def set_charset
headers['Content-Type'] = "text/html; charset=utf-8"
end
# Pick a unique cookie name to distinguish our session data from others'
session :session_key => '_nonror_session_id'
private
def setblog
if !params['blogname']
$NCGlobals['blog'] = Blog.find(CONF['DefaultBlog'])
else
$NCGlobals['blog'] = Blog.find_by_bname(params['blogname']) || Blog.find(CONF['DefaultBlog'])
end
end
def setcatid
if params['catid']
$NCGlobals['catid'] = params['catid'].to_i
end
end
public
def index
setblog
setcatid
$NCGlobals['skin'] = $NCGlobals['blog'].skin
$NCGlobals['skintype'] = 'index'
$NCGlobals['item'] = Array.new
$NCGlobals['controller'] = self
@contents = $NCGlobals['skin'].part($NCGlobals['skintype'])
@controller = NCParser::DefaultSkinIndex.new $NCGlobals['controller']
end
def item
setblog
setcatid
$NCGlobals['skin'] = $NCGlobals['blog'].skin
$NCGlobals['skintype'] = 'item'
$NCGlobals['item'] = [Item.find(params['itemid'].to_i)]
$NCGlobals['controller'] = self
@contents = $NCGlobals['skin'].part($NCGlobals['skintype'])
@controller = NCParser::DefaultSkinItem.new $NCGlobals['controller']
render :action=>'index'
end
end今,actionはindexとitemしかありませんが,itemの最後には「render :action=>'index'」というのが入っています。これは表示用のビュー・ファイルはindex.rhtmlを使うということ。全スキンで共通にしています。といってもビュー・ファイルの中身は「<%= parse %>」だけ。これを処理するのはapplication_helper.rbで,この中身もいたってシンプル。
# Methods added to this helper will be available to all templates in the application.
module ApplicationHelper
def parse
@controller.parse(@contents)
end
end
ここで@controllerというのがパーサのオブジェクトで,それがスキンの中身をパースするという仕組みです。これでパーサが実行した結果が出力されます。これが基本的な流れです。