2007年5月20日日曜日

Ruby on Rails notes

Rails に関するページや本をざっとみてこれはデータベース依存の大きな
フレームワークで、色々な制約があって単純に Web Application を移植
する対象として見れないのではないかという印象を受けていたが、よく
見て行くと、そうでもないらしい。

単純なケースとして rails で作成したプロジェクトにひとつコントローラを
設けるだけでページとして機能する。

> rails simple
> cd simple
> ruby script\generate controller info
> notepad app\views\index.rhtml

これだけでよい。以前、色々試したときの経験から自動生成されている
config\database.yml の設定が邪魔をするかとも心配したが、モデルが
何もなければデータベース接続は発生しない。

次のステップとしてリンクやフォームの処理が気になってくる。

コントローラの中には次の様な記述がよく現れる。
render :action => 'new'

ruby では推測可能な部分の括弧は書かなくてもよいという特徴がある。
なんとなくハッシュを渡しているのはわかるが、なぜキーがシンボル
(:name :に続けて識別子のようなもの)なのか今ひとつ不明。
http://www.rubyonrails.org/ から API のページを見てみる。
http://api.rubyonrails.org/ 色々と説明はあるのだが今ひとつ
仕組みがぴんとこない。

http://www.rubyonrails.org/ に Source のリンクがある。
そこに SVN の URL がある。ブラウザで開いてみるとソースをブラウズ
出来た。
http://svn.rubyonrails.org/rails/branches/1-2-stable

API の各ページのはじめに定義があるファイル名が表示されている。
そのパスを参考に実際のソースを見ることが出来る。

render の場合は次のパス。
http://svn.rubyonrails.org/rails/branches/1-2-stable/actionpack/lib/action_view/base.rb

link_to の場合は次のパスになる。
http://svn.rubyonrails.org/rails/branches/1-2-stable/actionpack/lib/action_view/helpers/url_helper.rb

これらのソースに symbolize_keys というメソッドがハッシュに対して
よばれているところがある。ハッシュに関するメソッドかと思い、小さな
スクリプトで動作を確認しようとすると、そんなメソッドはないといわれて
しまう。

Rails の API を見ると vendor/rails/activesupport/lib/active_support/core_ext/hash/keys.rb
の中で定義されている。パスの名前から (core_ext) rails の activesupport
でハッシュの機能を拡張していることのように見える。

activesupport を require して試してみようとも思ったが rails の
コントローラで試しても同じことだろうと思ってコントローラの中で
ハッシュに対して symbolize_keys をハッシュに対して呼んでみる。

その結果 "abc" => "ABC" が :abc => "ABC" のように変わって
入ることがわかった。これからいくと render/link_to の引数で

render :action => 'new'

と書くのは

render 'action' => 'new'

と同じということだろう。ではなぜか?

おそらく、文字列のためのメモリをセーブするためではないか。
Java でいうところの String.intern() にあたるのではないかと
おもう。

----------
では上記の render 文が何をするかということだが、action で
指定された view をクライアントに返すということのようだ。
極端な場合、コントローラーアクションは色々定義しても
ひとつの view (rhtml file) で表示させることも出来る。

コントローラを作ると ApplicationController のサブクラスが
作られるがこの中にアクションに対応したメソッドを定義して
おくことで呼び出すことが出来る。


def list
@item_pages, @items = paginate :items, :per_page => 10
end


この後、アクションと同じ名前 + .rhtml で表示が行われる。

index.rhtml に関しては index というアクションがなくても
良いようだ。また、'index' というアクションで index ページ
を表示させることもできる。

----------
.rhtml (embedded ruby) の側ではリンクに次の様なものが現れる。

<%= link_to 'Show', :action => 'show', :id => item %>
<%= link_to 'Previous page', { :page => @item_pages.current.previous } if @item_pages.current.previous %>

link_to も Rails のメソッドで API doc を見て行くと仕様がわかる。

link_to(name, options = {}, html_options = nil, *parameters_for_method_reference)

最初のケースでは :id => item という引数を渡しているがここには任意の
物が指定できるようだ。ただし、受けたコントローラでは symbolize_keys
の逆操作が行われているようで、key を :myparam としてもコントローラで
params をダンプしてみるとキーは "myparam" となっている。ただし、
依然として params[:myparam] での参照は可能。

また、item は item.id と同意ということのようだが、これがどこからきている
かは今のところはっきりしない。

----------
http://railsapi.masuidrive.jp
AJAX を使用した部分的に日本語化された rails のマニュアル。

----------

def list
@item_pages, @items = paginate :items, :per_page => 10
end


これはレコード数が多くなる可能性のあるページに使うイディオムの
様だが左辺が二つある。よく考えると多重代入だし pagenate の API
にも pagenator と collection を返すと書いてある。

複数の値を返すという概念は C++/Java にはないので少し困惑したが、
多重代入の本質はリストを返すことと、それをどのように代入、グルーピング
するかということで、メソッドの最後でリストを評価すればいいと言う
事。便利だが、仕様をしっかり書いておく必要がある。

----------
フォーム


<% form_tag :action => 'escape' do %>
<%= text_area 'etext', 'rawtext', :rows => 8 %><br/>
<%= submit_tag "Escape" %>
<% end %>


Form は form_tag を使って作ることが出来る。:action にポストを
受けるアクションの名前を指定する。
text_area の引数に迷ってしまったが、適当でもポストはできた。
ただ、同じ view でうけると表示上のデータは消えてしまう。
要は部品にバインドできていない状態だ。

これは最初の引数をコントローラのフィールドのドメインオブジェクトの
名前に、二番目をそのフィールドにすることで解決できたようだ。
上記の例では


def escape
@etext = Etext.new
@etext.rawtext = params[:etext][:rawtext]
@etext.escape
render :action => "index"
end


Etext は app/models/etext.rb で次のように定義している


class Etext
attr_accessor :rawtext
attr_accessor :escaped_text
def escape
@escaped_text = @rawtext.gsub(/&/, '&amp;').
gsub(/</, '&lt;').gsub(/>/, '&gt;');
end
end


生成される HTML は次のようになる。


<form action="/info/escape" method="post">
<textarea cols="40" id="etext_rawtext"
name="etext[rawtext]"
rows="8">...</textarea><br/>
<input name="commit" type="submit"
value="Escape" />
</form>


ポストされたデータは params という名前のハッシュで参照できて
次のようになっている。

{"commit"=>"Escape", "action"=>"escape",
"controller"=>"info", "etext"=>{"rawtext"=>"..."}}
----------
||=

これは変数が nil の場合のみ右辺値を左辺値に代入するというオペレータ

----------
variable.nil?

これは変数が nil かどうかをテストするメソッド。未定義、未設定のものに
テストできるのは面白い。C++/Java でやれば例外、エラーになるところだ。

----------
@rawtext.gsub(/&/, '&')

gsub() は指定された置き換えの結果を返すが gsub!(...) では元の
文字列が書き換えられる。! は破壊的動作のしるし。C++ でいうところの
非 const 関数といったところか。? は問い合わせ
をするようなメソッド名の最後につけておくという慣例らしい。

----------
クラス名は大文字で始める必要がある。

フィールドの定義は attr_accessor でできる。引数はフィールド名の
前に : を付けた物(シンボル)

attr_accessor :rawtext
attr_accessor :escaped_text

----------
Model の定義に関係を belongs_to などで指定できる。


class ListItem <ActiveRecord::Base
belongs_to :user
end


一見、設定ファイルに関係性を明示しているようにも見えるが(案外、それが
目的の設計かも知れないが) これは Ruby のクラス定義である。では
belongs_to は一体何なのか。これも API をたどって行ってわかったが、
これは ActiveRecord::Base のクラスメソッドである。クラスメソッドが
このようなところに書いてあると何が起こるかというとクラスがロード
されるときにこのクラスメソッドが実行される。なるほどね~。

----------
Windows 上のファイル編集には Terapad と cygwin 内の vim を使っていた。
たまたまフリーの EmEditor をいうのを見つけたので使ってみると Terapad
の Tab 版といった感じでなかなかよさそう。デスクトップがごちゃごちゃに
ならないところがいい。

----------
view の中では controller のインスタンス変数を文法上そのままアクセス
できるが、これはリフレクションのような手法を使って巧みに作り込まれた
物らしい。controller オブジェクトだが、シングルトンではないらしい。
あるアクションで設定したインスタンスフィールドを別なアクションで
表示してみると nil になっていた。

----------
最初は暗号のように見えたコントローラや rhtml の記述もその意味がわかって
くるとなかなか味がある。Ruby は以前、挑戦したのだが、その時点では
将来性が良くわからず直ぐに忘れてしまった。数年を経て C++ やら Java を
(自分としては)深く使い込んだ今見直してみると中々興味深い言語だ。
Rails はその機能をうまく拡張、利用しているようだ。

native library 依存がない物であればほぼ jruby で動くというところも
すばらしい。ruby - bytecode 変換が早く実現されないかとたのしみでもある。

0 件のコメント: