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年3月5日土曜日

Ubuntu 10.04 で快適音楽生活

Ruby, scala の開発しかやってなかったけど、遊びでつかってみようと思い色々やり始めた。
mp3 を管理する際のメモ書き。(イロイロやらないといけないので)
* 基本的に追加ソフトとかは極力インストールしない方針です
なので、利用するアプリは
  • Rhythmbox 0.12.8 (音楽ファイル管理)
  • Brasero (CD作成ツール)
となる。

1.mp3 の ID3 タグを編集
文字化けしている mp3 が多数あったのでそれを一つ一つ直していくのは時間の無駄と思い
Easy TAG をインストール (いきなりソフトを追加してしまった)
sudo aptitude install easytag
[アプリケーション]→[サウンドとビデオ]から Easy TAG を起動して、
[設定]→[設定]を開き、[ID3タグの設定]タブを選択。
ID3v1 タグの ID3x1.x でタグを書き込むのチェックを外す。
ID3タグ読み込み時の文字セットの規格外チェックを選んで、日本語(Shift-JIS)を選択。
これで選択した mp3 ファイルのタグ情報が文字化けしないで表示されているので、
[ファイル]→[ファイルの強制保存]を選んでタグを書き込む。

2.Brasero での音楽CD作成
この御時世に未だ音楽CDなるものを作成しているのですが。。
mp3 → 音楽CD へのライティングは普通にできないので、コーデック類をインストールする。
sudo aptitude install gstreamer0.10-ffmpeg gstreamer0.10-plugins-bad gstreamer0.10-plugins-bad-multiverse gstreamer0.10-plugins-ugly gstreamer0.10-plugins-ugly-multivers
これで、Brasero の音楽CD 作成プロジェクトを選択して mp3 ファイルをドロップすれば作成できるようになる。

2011年3月3日木曜日

Ruby: 指定された日が何月の何周目かを求める

タイトルのようなものが必要になった。
考えるのが面倒くさかったため、ネットで答えを探したが
うまく見つけることができなかったため、固い頭を使って
考えてみた。
その結果を晒します。

■仕様
  • 月曜日基点で考える。
  • 月曜日が所属する月にその週は属する。
例でいうと以下のとおり。(2011-03-03の場合)
2011年
2月-3週 21 22 23 24 25 26 27
2月-4週 28 1 2 3 4 5 6
3月-1週 7 8 9 10 11 12 13

実際のコードは以下。
def mweek(date)
  day = date - (date.cwday - 1)
  base_month = day.month

  week = 0
  (1..5).each do |index|
    day -= 7
    if base_month != day.month
      week = index
      break
    end
  end

  { :month => base_month, :week => week}
end

解説:
cwday で週を数字化して月曜日が 基点になるように補正。日付を移動させる。
あとは基点月が所属する月から何周目かをカウントするだけ。
復帰値は月と何周目かの Hash 値。複数の復帰値が返せる curl や scala が便利と感じる。
*よくテストしていないので、BUGがあるかもしれません。