ラベル rails3 の投稿を表示しています。 すべての投稿を表示
ラベル rails3 の投稿を表示しています。 すべての投稿を表示

2011年4月8日金曜日

Rails3 routes

やはり触れることになってしまった routes.rb。これを期に意味不明な部分を理解しよう。
勉強して得られた情報をメモ。

routes.rb にて生成される URL については rake routes コマンドにて確認できる。
記載する出力結果はすべて rake routes にて出力された結果です。


match(path, options={})


route 師弟の基本。アクセス可能な path を指定してやることで、HTTP リクエストを処理できるようになる。
match "foo/bar"
# foo_bar  /foo/bar(.:format) {:controller=>"foo", :action=>"bar"}
path は必ず一つ以上 "/" を入れてやる必要がある。
  • match "foo" => NG
  • match "foo/bar" => :controller=>"foo", :action=>"bar"
  • match "foo/bar/buz" => :controller=>"foo/bar", :action=>"buz"
"/" を複数指定した場合、controller の namespace を表すように解釈される。

以下指定可能オプション。(全部じゃないかも)

:controller
:action
動作させるコントローラとアクションを指定する。
必ずセットで指定する。
path に "/" を入れなくてもこのオプションを指定してやれば解釈されるようになる。
match "foo", :controller=>"foo", :action=>"bar"
# foo  /foo(.:format)  {:controller=>"foo", :action=>"bar"}
:to :controller, :action の短縮形。"#" でコントローラとアクションを区切って記述する。
match "foo", :to=>"foo#bar"
:to の記述自体を短縮する方法もある。
match "foo"=>"foo#bar"
:via HTTP メソッドを付加する。
match "foo/bar", :via=>:get
# foo_bar GET /foo/bar(.:format) {:controller=>"foo", :action=>"bar"}
:as ルート名を指定する。ルート名は form_tag や link_to などで指定できる名前で、その名前に該当する path を生成して form を作成してくれる。
match "foo/bar", :as=>"bar"
# bar GET /foo/bar(.:format) {:controller=>"foo", :action=>"bar"}


get

post

put

delete


match の :via オプションの HTTPHelper メソッド。
指定可能なパラメータは match と同様。なので
get "foo/bar", :via=>:delete
なんていうふざけた記述もちゃんと解釈してくれる。(HTTP メソッドは GET になります)


resource

resources


Rails のルールに従って RESTful 的な URL を自動で生成してくれる有名なメソッド。
単数形の場合は resource を、複数形の場合は resources を利用する。*指定する名前も単数形、複数形を意識して指定してやる。(resource :foo, resources :foos)
二つの違いは一覧出力 URL (scaffold 生成時の index アクション) の有無の違い。

以下指定可能オプション。(全部じゃないかも)
:as ルート名に利用する別名。
resource "foo", :as=>"bar"
#   bar POST   /foo(.:format)      {:action=>"create", :controller=>"foos"}
# new_bar GET    /foo/new(.:format)  {:action=>"new", :controller=>"foos"}
#edit_bar GET    /foo/edit(.:format) {:action=>"edit", :controller=>"foos"}
#       GET    /foo(.:format)      {:action=>"show", :controller=>"foos"}
#       PUT    /foo(.:format)      {:action=>"update", :controller=>"foos"}
#       DELETE /foo(.:format)      {:action=>"destroy", :controller=>"foos"}
:controller 処理するコントローラを指定する。
アクション名は Rails ルールに従う必要がある。
resource "foo", :controller=>"bar"
#   foo POST   /foo(.:format)      {:action=>"create", :controller=>"bar"}
# new_foo GET    /foo/new(.:format)  {:action=>"new", :controller=>"bar"}
#edit_foo GET    /foo/edit(.:format) {:action=>"edit", :controller=>"bar"}
#        GET    /foo(.:format)      {:action=>"show", :controller=>"bar"}
#        PUT    /foo(.:format)      {:action=>"update", :controller=>"bar"}
#        DELETE /foo(.:format)      {:action=>"destroy", :controller=>"bar"}
:path URL を置き換える。
resource "foo", :path=>"b/a"
#   foo POST   /b/a(.:format)  {:action=>"create",:controller=>"foos"}
# new_foo GET    /b/a/new(.:format) {:action=>"new",:controller=>"foos"}
#edit_foo GET    /b/a(.:format)     {:action=>"edit",:controller=>"foos"}
#         GET    /b/a(.:format)     {:action=>"show",:controller=>"foos"}
#         PUT    /b/a(.:format)     {:action=>"update",:controller=>"foos"}
#         DELETE /b/a(.:format)     {:action=>"destroy",:controller=>"foos"}
:only 作成される URL を絞り込む。指定されたアクションのみ URL が生成される。
resources "foo", :only=>["index"]
# foo_index GET /foo(.:format) {:action=>"index", :controller=>"foo"}
:except 作成する URL を削除する。指定されたアクションは URL が生成されない。
こう書けば、:only と同じ結果になる。
resources "foo", :except=>["create","edit","new","show","update","destroy"]
# foo_index GET /foo(.:format) {:action=>"index", :controller=>"foo"}
:module controller に namespace を付加する。
resources "foo", :only=>["index"], :module=>"module"
# foo_index GET /foo(.:format) {:action=>"index", :controller=>"module/foo"}

has_many の関係を定義する場合、block を渡して resources を定義する。

resources "foo" do
  resources "bar"
end


scope


URL に namespace を付けるイメージ。controller には付かない所がポイント。
scope "scope" do
  resources "foo", :only=>["index"]
end
# foo_index GET /scope/foo(.:format) {:action=>"index", :controller=>"foo"}

block 内に match(path) を指定した場合、namespace として扱われる。(controller にも scope が付加される)
scope "scope" do
  match "foo/bar"
end
# foo_bar  /scope/foo/bar(.:format) {:controller=>"scope/foo", :action=>"bar"}
:to オプションを付けて controller と action を宣言してやれば、URL のみ対象となる。

以下指定可能オプション。(全部じゃないかも)
:module controller の namespace を指定する。
scope "sco", :module=>"mod" do
  resources "foo", :only=>["index"]
end
# foo_index GET /sco/foo(.:format) {:action=>"index", :controller=>"mod/foo"}
:as ルート名に prefix を付加する。resources は別名に置き換えるがこっちは付加する。
scope "sco", :as=>"as" do
  resources "foo", :only=>["index"]
end
# as_foo_index GET /sco/foo(.:format) {:action=>"index", :controller=>"foo"}

controller のみに namespace を付加したい場合、
scope :module=>"module" do
  resources "foo", :only=>["index"]
end
としてやればよい。


namespace


URL 名, URL と controller に(要は全てに) namespace を付加する。
namespace "nspe" do
  resources "foo", :only=>["index"]
end
# nspe_foo_index GET /nspe/foo(.:format) {:action=>"index", :controller=>"nspe/foo"}

以下指定可能オプション。(全部じゃないかも)
:as ルート名に prefix を付加する。動きは scope と同じ。


controller


controller を一括で指定できる。
controller "foo" do
  get "search"
end
# search GET /search(.:format) {:action=>"search", :controller=>"foo"}
match でエラーになるパターン ("/" なし) も controller を付加してくれてちゃんと動く。
優先度は block 内のメソッドの方が高く、resources "bar" や match "bar/buz" など指定しても適用されない。(:controller=>"bar"になる)

以下指定可能オプション。(全部じゃないかも)
:path URL に prefix を付加する。デフォルトでは controller 名は URL に付加されないので、必要な場合は指定する。
controller "foo", :path=>"foo" do
  get "index"
end
# index GET /foo/index(.:format) {:action=>"index", :controller=>"foo"}
:as ルート名に prefix を付加する。動きは scope と同じ。


root


root ("/") にアクセスされた際の routes を指定する。
root :to=>"foo#bar"
# root  /(.:format) {:controller=>"foo", :action=>"bar"}



長文でダラダラとまとめたが、まだまだ全部でない感じがする。(ソース見た感じ)
けど、これで routes への恐怖心が若干薄まった感がある。

2011年3月30日水曜日

Rails3 :method => :delete が動かない件

環境
  • Windows XP SP2
  • Internet Explorer 6
  • ruby 1.9.2p136 (2010-12-25) [i386-mingw32]
  • Rails 3.0.3

上記環境にて、link_to :method => :delete が正しく動作しない。
scaffold で生成したコンテンツでもダメだった。
原因を調査してみたら、rails.js 内の以下の箇所で TypeError が発生していた。
function handleMethod(element) {
  var method = element.readAttribute('data-method'),
      url = element.readAttribute('href'),
      csrf_param = $$('meta[name=csrf-param]')[0],
      csrf_token = $$('meta[name=csrf-token]')[0];

  var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
  element.parentNode.insert(form); // ←←←この箇所

  if (method !== 'post') {
    var field = new Element('input', { type: 'hidden', name: '_method', value: method });
    form.insert(field);
  }

  if (csrf_param) {
    var param = csrf_param.readAttribute('content'),
        token = csrf_token.readAttribute('content'),
        field = new Element('input', { type: 'hidden', name: param, value: token });
    form.insert(field);
  }

  form.submit();
}

debug してみると、たしかに element.parentNode に insert という function は存在していなかった。
とりあえず、以下のように修正することで回避可能。
//  element.parentNode.insert(form);
  element.insert(form);

以下の環境だと、上記修正を行わなくてもちゃんと動いた。
  • Ubuntu 10.04
  • Google Chrome 10, Firefox 4.0
  • ruby 1.9.2p136 (2010-12-25 revision 30365) [i686-linux]
  • Rails 3.0.4

怪しいのは IE6。別バージョンで試すことができない環境なのでどうしようもない。
んー、おかしい。

2011年3月26日土曜日

Rails3 selectbox を動的に変更する。

Rails3 で selectbox (combobox) の中身を動的に入れ替える処理が必要になった。
ググってみると大概 observe_field を利用した Ajax.update を行うものだったが、Rails3 には
observe_field が削除されている。(正確には Rails2.3.9 で削除されたっぽい)
なので無い脳ミソをフル稼働させて考えてみた結果をメモ。


■仕様(やりたい事・目指した事)

  • selectboxA を変更したら、その内容で selectboxB の内容が置き換わる。
  • n段に対応。(selectboxA → selectboxB → selectboxC → ... → selectbox(N))
    今回は大分類・中分類・小分類の 3 段で考える。
  • 全ての selectbox の先頭に :inclide_blank => true を入れる。
  • DRY を徹底する。(同じコードはなるべく書かない)

調べてみるとやり方はかなり豊富。仕様と照らし合わせてどのやり方をチョイスするか選んだ方がよさそう。
今回の仕様から、RJS つかって動的にコンテンツを更新する方法にした。


■考え方

まず selectbox のデータを格納する model を考える。今回は kind(分類) という model を作成する。
kind
PKid : integer
 name : string
FKkind_id : integer
kind_id が外部キーとなっており、自分の親のキーを保持する。
(中分類のデータなら所属する大分類のIDを保持する)

次に html タグの動的入れ替えだが、以下の範囲を入れ替えるように考えた。
<p id="middle_select">
<select id="kind_id_middle"> <option></option> *黄色の部分を Ajax で入れ替える </select>
</p>
理由は、selectbox の先頭に blank option をつけたかったから。直に <option value=""></option>
タグを書けば option 範囲の入れ替えでもいけるのだが、それはあまりやりたくなかった。(要こだわり)


■実装

model は作ってあり、データも適当にいれているものとする。
大分類の kind_id は 0 を指定しておく。(TOP レベルで親はいませんという意味で)
必要な routes.rb は記述してあるものとする。
参考データ
idnamekind_id
1大分類10
8中分類11
22中分類21
43小分類18
44小分類28
65小分類322

初期表示時の view。
# select.html.erb

<%= label "kind_id", "large", "大分類:" %> <%= select "kind_id", "large", Kind.where("kind_id = 0").map{|p| [p.name, p.id]}, {:include_blank => true}, {:onchange => remote_function(:url => {:action => "change_select"}, :with => "'kind_id[large]=' + escape(this.value)")} %>

<%= render :partial => "middle_select", :locals => {:middle_kinds => @middle_kinds} %>

<%= render :partial => "small_select", :locals => {:small_kinds => @small_kinds} %>

ポイントは
  • render 使って動的入れ替えする部分を外だしにする。(RJS でも render すれば DRY になる)
  • observe_field がないので、onchange イベントに remote_function ひっつける。
  • remote_function :with で大分類の値を POST する。
  • 複数のコンテンツをいっぺんに変えるため (大分類 → [中分類, 小分類]) remote_function :update は未指定。

中分類の view。select.html.erb と同じ階層に作成する。
# _middle_select.html.erb
<%= label "kind_id", "middle", "中分類:" %>
<%= select "kind_id", "middle",
      middle_kinds.map{|p| [p.name, p.id]},
      {:include_blank => true},
      {:onchange => remote_function(:url => {:action => "change_select"},
                                    :with => "'kind_id[middle]=' + escape(this.value)")} %>

小分類の view。select.html.erb と同じ階層に作成する。
# _small_select.html.erb
<%= label "kind_id", "small", "小分類:" %>
<%= select "kind_id", "small",
          small_kinds.map{|p| [p.name, p.id]},
          {:include_blank => true} %>
中分類には選択したら小分類を入れ替える必要があるので onchange イベントを追加するが、小分類は入れ替え処理が不要なので、onchange は省略する。

次は controller。
# controller.rb
# 初回表示時 action
def select
  @middle_kinds = []
  @small_kinds = []
end

# onchange 時のイベント
def change_select
  if params[:kind_id][:large]
    @middle_kinds = params[:kind_id][:large] != "" ?
        Kind.where("kind_id = #{params[:kind_id][:large]}") :
        []
    @small_kinds = []
  else
    @middle_kinds = nil
    @small_kinds = params[:kind_id][:middle] != "" ?
        Kind.where("kind_id = #{params[:kind_id][:middle]}") :
        []
  end
end
初回表示時は大分類だけに値が設定されているシチュエーションなので、中分類、小分類は空にしておく。
change_select イベントは大分類、中分類で共同で利用するイベント。params[:kind_id][:large]が存在すれば(nil でなければ)大分類の change イベントと判別できるので、大分類変更時は、中分類を読み込んで小分類をリセットする。
中分類変更時は (params[:kind_id][:middle] != nil) 小分類のみ書き換えするため、あえて @middle_kinds に nil を設定して書き込み対象外の判別が行えるようにした。(後述 RJS 参照)

最後に RJS。select.html.erb と同じ階層に作成。
# change_select.js.rjs
if @middle_kinds
  page.replace_html "middle_select", :partial => "middle_select", :locals => {:middle_kinds => @middle_kinds}
  page.visual_effect :highlight, "middle_select"
end
page.replace_html "small_select", :partial => "small_select", :locals => {:small_kinds => @small_kinds}
page.visual_effect :highlight, "small_select"
@middle_kinds == nil 時は更新不要なので、更新するかしないかを最初に判定している。
一応、視覚的効果があるほうがインターフェース的に親切らしいので、動的入れ替え後は effect した。


■最後に

今回は複数更新することを念頭に入れていたため RJS を利用したが、1個のみの更新 (大分類 → 中分類) だけだったら、remote_finction :update => "middle_select" を指定して、controller で render :partial => "middle_select" ってする方が簡単。けど、こっちの方が応用 & 拡張性に優れている気がするので、RJS 積極的に使っていこうかな。

以上。長文でわかりづらく、失礼しました。

2011年3月25日金曜日

Rails3 text_field と text_field_tag の違い

なんか、今更だけどやっと違いが分かったのでメモ。

text_field_tag

<%= text_field_tag 'user_id' %>
<input id="user_id" name="user_id" type="text" />

text_field

<%= text_field 'person', 'user_id' %>
<input id="person_user_id" name="person[user_id]" size="30" type="text" />

違いは
  • size 属性が自動でつく。
  • POST パラメータがハッシュ形式になる。params[:user_id] と params[:pserson][:user_id] の違い

一番大きいのは、パラメータ値がハッシュ形式で取得できるかどうかの部分。

他の ヘルパメソッドもすべて同じ。(password_field や check_box など)

Rails 1.x から時間の合間見て触ってきて、やっと理解できた俺っていったい。。。(by キートン山田)

Rails3 fields_for

使い道は思いつかないが、便利そうなのでメモ。
form_for は <form></form> を生成するが、fields_for は <form> を生成しない。
で、form_for と同じ POST 時のパラメータ値を Hash 形式にできる。

■form_for

<%= form_for :person do |f| %>
  ID: <%= f.text_field :user_id %>
  Name: <%= f.text_field :name %>
<% end %>
<form accept-charset="UTF-8" action="/form/input" method="post"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="✓" /><input name="authenticity_token" type="hidden" value="Rtch9vUwmDWmniDy/IsMetQhhfnbzp9lHXV6YAe2PwA=" /></div>
  <input id="person_user_id" name="person[user_id]" size="30" type="text" />
  <input id="person_name" name="person[name]" size="30" type="text" />
</form>

■fields_for

<%= fields_for :person do |f| %>
  ID: <%= f.text_field :user_id %>
Name: <%= f.text_field :name %>
<% end %>
ID: <input id="person_user_id" name="person[user_id]" size="30" type="text"/><br/>
Name: <input id="person_name" name="person[name]" size="30" type="text"/>

以前書いた check_box の配列化なんかのコードが
<%= fields_for :dinner do |field| %>
  <%= field.check_box "zensai" %>前菜
  <%= field.check_box "supe" %>スープ
  <%= field.check_box "main" %>メインディッシュ
  <%= field.check_box "dezart" %>デザート
  <%= field.check_box "drink" %>ドリンク
<% end %>
と、なんかスッキリと書ける。

Rails3 HTML5 タグの出力

Rails3 で HTML5 サイト作成のため、イロイロ調査した結果をメモ。

HTML5 タグを吐き出す基本的なやり方は、
<%= text_field_tag 'find', nil :type=>'search' %>
<input id="find" name="find" type="search"/>
と、text_field_tag の html_options で type 属性を指定する。
Rails ではちゃんと上記コードを簡略化するためのヘルパメソッドが用意されている。


HTML TagFormHelper Method
<input type="search"/>search_field
<input type="tel"/>telephone_field(phone_field)
<input type="url"/>url_field
<input type="email"/>email_field
<input type="number"/>number_field
<input type="range"/>range_field

日付系 (date, datetime 等) や color といったものは見当たらなかった。
なので、ヘルパメソッドが存在しないタグは text_field を直接利用するしかない(?)

2011年3月22日火曜日

Rails チェックボックスの配列化(修正)

昔、こんな記事を書いたが、
こんな配列化は意味がないことに気づいたので加筆。

check_box_tag

以下のように記述すると、
<%= check_box_tag "print" %>印刷する
生成される HTML は
<input id="print" name="print" type="checkbox" value="1" />印刷する
となる。
submit した際の parameter 値は
選択時 => "print"=>"1"
非選択時 => "print"=>nil
となる。
ruby では false と nil は false 扱いとなるため、単純に if で params[:print] と判定すれば
ON, OFF の判定ができるため、簡単に記述できる。

check_box

以下のように記述すると、
<%= check_box "print", "enabled" %>印刷する
生成される HTML は
<input name="print[enabled]" type="hidden" value="0" /><input id="print_enabled" name="print[enabled]" type="checkbox" value="1" />印刷する
となる。
submit した際の parameter 値は
選択時 => "print"=>{"enabled"=>"0"}
非選択時 => "print"=>{"enabled"=>"1"}
となる。
要は、check_box タグを利用すると、checkbox を Hash でパラメータ渡しできるようになる。
なので、配列化したい場合は、それぞれの項目に名前をつけてやって Hash の key として扱ってやればよい。

例) ディナーのコースを選択する。必要なコースがあればチェックをつける
<%= check_box "dinner", "zensai" %>前菜
<%= check_box "dinner", "supe" %>スープ
<%= check_box "dinner", "main" %>メインディッシュ
<%= check_box "dinner", "dezart" %>デザート
<%= check_box "dinner", "drink" %>ドリンク
これで、ディナーパラメータ値にハッシュとして配列化されてコントローラに渡ってくる。

無理に配列化するメリットは何もない。意味不明な index で値を取得する必要も
ないし、未選択時に飛んでこないなんてこともない。

2011年2月6日日曜日

Rails3 Bundler

Rails アプリ毎に Gemfile の依存関係を管理するための仕組み。

$RAILS_APP 直下にある Gemfile を編集する。

# Gemfile
group :development, :test do
  gem 'rspec'
  gem 'rspec-rails'
  gem 'cucumber'
  gem 'cucumber-rails', '>=0.4.0.beta.1'
end

Gemfile のインストール
bundle install vendor/bundle

インストール先の推奨は vendor/bundle らしい。
未指定の場合、ruby/gems 配下にインストールされ、全環境に影響を与えるようになる。

Gemfile の確認
bundle list

削除方法は不明。
とりあえず vendor/bundle 配下をすべて削除して clean インストールする。