カテゴリ: Rails

ActiveRecordで取得したテーブルのデータから選択リスト(プルダウン)を作成したいと思ったんです。 出張費精算のアプリを作っていて、"http://localhost/home/list/200707"というURLにアクセスすると、2007年7月のリストを出力する仕様になっています。これにプルダウンで月の選択をすると、リストが切り替わる様にしたいんです。 テーブルは月別の出張費精算テーブルで、こんな構成。
      create_table(:monthly_expenses, options) {|table|
        table.column :employee_id,        :integer
        table.column :appropriate_month,  :date
        table.column :period_from,        :date
        table.column :period_to,          :date
        table.column :total_amount,       :integer
        table.column :prepayment,         :integer
        table.column :report_date,        :date
        table.column :status,             :integer
        table.column :time_stamp,         :datetime
      }
選択リストはこの月別のリストを切り替える為に使いたいので、欲しいのは、" appropriate_month"だけだったりします。 ずいぶんと余分なモノが混じってしまっています。それだけならまだしも、URLとして使う都合上、"yyyy-MM-dd"形式ではなくて、"yyyyMM"を取得したい。選択リストのテキストには"yyyy年MM月"としてあげたい。 どうやっても変換をかける必要があるんですが、出来ればSQLを書かずにすませたい(単なるこだわりです)。 ActiveRecordに取得カラムを指定は出来ない様子。オブジェクト化するから、当然なんですけど。 取得結果をブロックで処理して、mapしてあげることにしました。
@monthly_list =
   MonthlyExpense.find(:all,
                :conditions => ["employee_id = ?", employee_id],
                :order => "appropriate_month"
                ).map { |m| [m.appropriate_month.strftime("%Y年%m月"),
                               m.appropriate_month.strftime("%Y%m")] }
あとは、rhtml側で、ActionHelperを使って、
<%= select(:month, :value, @monthly_list) %></pre>
としてあげれば、OK。 ではなくて、初期値を設定してあげないといけない。 selectの場合は、@month.valueに入っている値がリストにあれば、それを選択状態にしてくれるのですが・・・ monthオブジェクトはselectタグ内部で作成されていので、コントローラー側でセットしようにもオブジェクトが存在しない。 動的にオブジェクトを作成するほうほうが思い浮かばなかったので、クラス定義をしてまいました。
class Month 
  attr_accessor :value
end
@month = Month.new @month.value = @monthly_expense.appropriate_month.strftime("%Y%m")
すっごく強引な気がするけど、後で思いついたら直すことに。 最後にonChangeのイベントをとってリクエストを投げて挙げるように修正。 prototype.jsのEvent.observeを使う。
Event.observe($('month_value'), 
   "change", 
   function(){
    document.location.href = "/home/list/" + escape($F('month_value'));
   });
これで一応動作を確認。なんかもっと上手くできる気がするんですけどね。 とりあえず動けば官軍って言うんでこれで我慢します。
このエントリーをはてなブックマークに追加

Railsのscaffoldで使われているerror_messages_forってすっごく便利なのですが、 これリストの入力フォームに対応していないんですよね。 上記の画面には2行のテーブルがあって、 交通費、宿泊費、その他の3レコードが含まれています。つまり3×2で6レコードです。 普通にerror_messages_forだとチェック自体は行われますが、エラーメッセージが最後の1レコード分しか出ませんでした。 というわけで、すべてのレコードのエラーを拾って重複を排除して表示する用にしてみました。 app/helpers/application_helperにメソッドを追加してもいいんですが、最近ActiveHeartを入れたばかりなので、 vender/plugin/active_heart/lib/active_recorde_message_ja.rbのerror_messages_forの下に下記コードを追加しておきました。
  def list_error_messages_for (object_name, options = {})
    objects = instance_variable_get("@#{object_name}")
    errors = Array.new
    for record in objects
      unless record.errors.empty?
        record.errors.each_full { |msg| errors.push(msg)}
      end
    end
    errors = errors.uniq
    unless errors.empty?
      options = options.symbolize_keys
      content_tag("div",
        content_tag(
          options[:header_tag] || "h2",
          "#{errors.size}個のエラーが発生しました"
        ) +
        content_tag("p", "次の項目に問題があります") +
        content_tag("ul", errors.collect { |msg| content_tag("li", msg) }),
                    "id" => options[:id] || "errorExplanation", 
                    "class" => options[:class] || "errorExplanation"
        )
    end
  end
ruby&rails初心者なので恥ずかしい限りですが、一応動作はしているみたいです。ツッコミ有ればご指導下さい。 つか、こんなのとっくに誰か作っている気もする。更に言えば確実に此処はRailsらしからぬ実装になってしまったかも。 エラーチェック自体は相変わらず適当(あえてエラー出しています)なのですが・・・ が、またしても此処で問題が。 詳細の項目には:allow_nilでnilやブランクの場合はチェックしないようにしているんですが、何故かチェックされてしまいます。他の所はちゃんと動作しているのに・・・ 近々攻略予定です。
このエントリーをはてなブックマークに追加

RailsのValidationを日本語化してみます。 使うのは、Rubyist Magazineで紹介されているActiveHeart。 Railsプロジェクトホームに移ってからsubversionを使ってインストール。
$ ./script/plugin install http://svn.rails2u.com/public/plugins/trunk/active_heart/
あとはサーバーを再起動するだけでOKです。 但し、これだけだと、 項目名が英語のまま。 これを日本語化するには、
class DailyExpense < ActiveRecord::Base
  set_field_names :description => '詳細', :amount => '金額', :currency_code => '通貨'
  validates_numericality_of :description, :on => :update, :allow_nil => true
  validates_numericality_of :amount, :on => :update, :allow_nil => true
  validates_numericality_of :currency_code, :on => :update, :allow_nil => true
end
と、set_field_namesを付けてあげれば良いらしい。ktkr validateの内容は適当なんで突っ込まないでください。 ”詳細”が数値ってなんの詳細だよ・・・ ところで、validatesの:allow_nilオプションなんですが、これを付けるとnilの場合はvalidateおこなわないんですねぇ。 なんでこんな重要な事早く言わないかなぁ。APIドキュメント見るまで気がつかなかった。単に、AWDwRにのっていなかったのだけど、当時はなかったのか・・・ ちなみにerror_messages_forのリスト対応したんですけど、 もう少し見直したいので、明日書くことにします。
このエントリーをはてなブックマークに追加

久しぶりにRailsをさわっていて気がついた。 validationってオブジェクトのコレクションに対応していないんではないか? active_record_helper.rbに定義されているerror_messages_forの部分を読んでみると、
# file active_record_helper.rb, line 103
def error_messages_for(object_name, options = {})
  options = options.symbolize_keys
  object = instance_variable_get("@#{object_name}")
  if object && !object.errors.empty?
    content_tag("div",
    content_tag(
    options[:header_tag] || "h2",
    "#{pluralize(object.errors.count, "error")} prohibited this 
       #{object_name.to_s.gsub("_", " ")} from being saved"
    ) +
    content_tag("p", "There were problems with the following fields:") +
    content_tag("ul", object.errors.full_messages.collect { |msg|
              content_tag("li", msg) }),
    "id" => options[:id] || "errorExplanation", 
          "class" => options[:class]
         || "errorExplanation"
    )
  else
    ""
  end
end
やっぱりか・・・ これだけなら、ActionHelperに複数オブジェクト用のメソッドを定義すれば良いけど、 更にテーブルを結合している場合は、そもそも更新をどうすれば良いのだろう? 色々頭に方法は浮かぶけど、一番Railsらしい書き方に未だ至らず、現在朝の3時半。 くそぉ、明日やるか・・・ くやしい くやしい くやしい くやしい 絶対明日攻略してやる!
このエントリーをはてなブックマークに追加

Migrationで初期データ作成

Migrationその1の続き Migrationで初期データの用意も出来ます。 開発時のユーザーデータとか、コードテーブルのようなマスタ定義を書いておくと、 複数人で開発するときに激しく便利。
$ ruby script/generate migrate insert_users
002_insert_usersというファイルが出来ているので、 テキストエディタで編集。
class InsertUsers < ActiveRecord::Migration
  def self.up
     User.create(:name=>"本田 忠勝"   , 
                 :password => "蜻蛉切り",
                 :birth => "1548-01-01",
                 :roll => "徳川四天王",
                 :time_stamp => Time.now
                 )
     User.create(:name=>"酒井忠次"   , 
                 :password => "東三河",
                 :birth => "1527-01-01",
                 :roll => "徳川四天王",
                 :time_stamp => Time.now
                 )
     User.create(:name=>"榊原康政"   , 
                 :password => "無",
                 :birth => "1548-01-01",
                 :roll => "徳川四天王",
                 :time_stamp => Time.now
                 )
     User.create(:name=>"井伊直政"   , 
                 :password => "赤備え",
                 :birth => "1561-01-01",
                 :roll => "徳川四天王",
                 :time_stamp => Time.now
                 )
  end
  def self.down
    @users = User.find_by_roll('徳川四天王')
    @ users.destroy
  end
end
すでにusersのmodelが出来ているはずなので、 User.createでレコードを生成出来ます。 self.downにはfind_by_XXXで検索してdestroyしてやれば良いでしょう。

Migrationで書き方あれこれ

初め迷ったのが、「Migrationで複数のテーブルを作る場合、一つのファイルに纏めるべきか?」と言う点です。 分けて書いても、一つに纏めても変わらないのですが、個人的には分けた方が良いと思います。 理由は単純でコードが見やすいからです。 例えば6テーブルMigrationで書いた場合、rake migrateした時にいきなりバージョンが6になるか1になるかの違いしかありませんし。 もう一つは、データはMigrationで用意すべきかfixtureで用意すべきか? どっちでもいい気がします。マスタデータの様なモノはMigrationで、それ以外はお好きなほうで。 YAMLのほうが編集はしやすいですが、本来はユニットテスト用なので、微妙な所です。 ターミナルからのfixtureのロードの仕方は・・・忘れたので後で書いておきます。
このエントリーをはてなブックマークに追加

↑このページのトップヘ