Object.tap

オブジェクトの状態を任意の場所で確認できるため、便利。従来のデバッグ方法のように構文を大きく崩す事なく実行できるところが良い。

class Object
  def tap
    yield self
    self
  end
end

使用例

blah.sort.grep( /foo/ ).tap { |xs| p xs }.map { |x| x.blah }

( k + 1 ) / ( ( q - t ).tap { |i| p i } / 2 )

def blah
  @things.map { |x|
    x.length
  }.inject( 0 ) { |a, b|
    a + b
  }.tap { |sum| p sum }
end

ActiveRecord::Errorsあたりのローカライズ

このサイトを参考にしました。というより全くこの通りにやりました。

validate_関連で、エラーが出た際にいろいろ英語がでてきちゃうのでその辺りを修正。まずは、フォームのエレメント名がそのままでちゃうのを防ぐために、modelの中で、下記のようにローカライズ

class Member < ActiveRecord::Base
  class << self
    HUMANIZED_ATTRIBUTE_KEY_NAMES = {
      "email"     => "メールアドレス",
      "password"  => "パスワード",
      "nickname"  => "ニックネーム",
      "gender"    => "性別",
    }

    def human_attribute_name(attribute_key_name)
      HUMANIZED_ATTRIBUTE_KEY_NAMES[attribute_key_name] || super
    end
  end

それから、"email is blank."とか出てくる" is blank"とかのメッセージをローカライズ。これは個々のvalidate_...でmessagesを指定すれば良いのだけど、毎回やるのはDRYじゃないので、まとめて設定。environment.rbの中に下記のように設定

ActiveRecord::Errors.default_error_messages[:inclusion]    = "が選択肢にありません" 
ActiveRecord::Errors.default_error_messages[:exclusion]    = "が予約されています" 
ActiveRecord::Errors.default_error_messages[:invalid]      = "が不正です" 
ActiveRecord::Errors.default_error_messages[:confirmation] = "が確認内容があっていません" 
ActiveRecord::Errors.default_error_messages[:accepted]     = "が許可されていません" 
ActiveRecord::Errors.default_error_messages[:empty]        = "が入力されていません" 
ActiveRecord::Errors.default_error_messages[:blank]        = "が入力されていません" 
ActiveRecord::Errors.default_error_messages[:too_long]     = "が長すぎます(最長%d文字)" 
ActiveRecord::Errors.default_error_messages[:too_short]    = "が短すぎます(最短%d文字)" 
ActiveRecord::Errors.default_error_messages[:wrong_length] = "が間違った長さです(長さ%d文字)" 
ActiveRecord::Errors.default_error_messages[:taken]        = "が既に使われています" 
ActiveRecord::Errors.default_error_messages[:not_a_number] = "が無効な数値です"

これでもまだ、エラーメッセージ全体を表示する<%=error_messages_for '...' %>の部分で英語がでてくるので、これの変わりになるヘルパーを作成。app/helpers/application_helper.rbの中に下記を追加

def template_error_messages_for (object_name, options = {})
  options = options.symbolize_keys
  object = instance_variable_get("@#{object_name}")
  unless object.errors.empty?
    render :partial=>"system/error_messages_for",
      :locals=>{:messages=>object.errors.full_messages, :object=>object}
  end
end

で、上で使うpartialを作成する。app/views/system/_error_messages_for.rhtmlとして、下記のように作成。mobile on railsを使っている場合はapp/views/views_mobile/system/_error_messages_for.rhtmlも作成する

<div class="errorExplanation" id="errorExplanation">
  <h2><%= messages.size %>個のエラーが発生しました</h2>
  <p>次の項目に問題があります</p>
  <ul>
    <% for mes in messages %><li><%= mes %></li><% end %>
  </ul>
</div>

あとはローカライズではないけど、エラーが出たときにフォームフィールドが<div class="fieldWithErrors">で囲まれてしまう問題を回避する。

が入ると改行が入ってしまうので携帯ブラウザでの表示に困る(cssで対応できない場合)。これはenvironment.rbに下記を追加

ActionView::Base.field_error_proc = lambda{|tag, instance| "<span class=\"fieldWithErrors\">#{tag}</span>" }

今日のrailsはこんな感じ。しかし検索で見つけ出した対応策が2005-08-02付けとかだったらしたら、もうobsoleteだろうとか思って不安になってくる。一応コードを試すとちゃんと動くんだけど、実際にはもっとスマートなやり方があるんじゃないかとか。まぁそういうのは使っていくうちに気づくだろう。

なぜSymbolを使うのか

よく分からないままrails流儀に従って:foobarとかやってたので、調べてみた。こういう事らしい。つまりは同じ文字列何個も作る度にメモリを確保しないようにする。従ってメモリが節約される。

携帯用に入力モード指定ヘルパー

mobile on railsというプラグインを入れてみたけど、これはとても便利。しかし、これには携帯の入力モードを指定できるようなコードは無かったっぽいので、その辺りを自作。

http://www.amazon.co.jp/exec/obidos/ASIN/4274066967/100hardco-22/ref=nosim/
22.6 カスタムフォームビルダーを参考に作ってみた。

まず、app/helpers/mobile_tag_builder.rbというファイルを作成して、中身を下記のように記述

class MobileTagBuilder < ActionView::Helpers::FormBuilder

  def text_field (field, options = {})
    if options.has_key?(:mobile_input_style)
      if @template.controller.request.mobile_carrier == ActionController::Mobile::DoCoMo
        options[:istyle] = mobile_input_style_by_carrier(options[:mobile_input_style]);
      elsif @template.controller.request.mobile_carrier == ActionController::Mobile::AU
        options[:istyle] = mobile_input_style_by_carrier(options[:mobile_input_style]);
      elsif @template.controller.request.mobile_carrier == ActionController::Mobile::SoftBank
        options[:mode] = mobile_input_style_by_carrier(options[:mobile_input_style]);
      end
      options.delete(:mobile_input_style)
    end

    super(field, options)
  end
  
  private
  def mobile_input_style_by_carrier (input_style)
    # ひらがな
    if input_style == :input_style_hiragana
      if @template.controller.request.mobile_carrier == ActionController::Mobile::SoftBank
        :hiragana
      else
        :'1'
      end

    # カタナカ
    elsif input_style == :input_style_katakana
      if @template.controller.request.mobile_carrier == ActionController::Mobile::SoftBank
        return :katakana
      else
        :'1'
      end

    # 半角カタナカ
    elsif input_style == :input_style_hankakukana
      if @template.controller.request.mobile_carrier == ActionController::Mobile::SoftBank
        :hankakukana
      else
        :'2'
      end

    # 英字
    elsif input_style == :input_style_alphabet
      if @template.controller.request.mobile_carrier == ActionController::Mobile::SoftBank
        :alphabet
      else
        :'3'
      end

    # 数字
    elsif input_style == :input_style_numeric
      if @template.controller.request.mobile_carrier == ActionController::Mobile::SoftBank
        :numeric
      else
        :'4'
      end

    # なんでも
    elsif input_style == :input_style_any
      if @template.controller.request.mobile_carrier == ActionController::Mobile::SoftBank
        :any
      else
        :'1'
      end

    # メールアドレス
    elsif input_style == :input_style_emailaddr
      if @template.controller.request.mobile_carrier == ActionController::Mobile::SoftBank
        :emailaddr
      else
        :'3'
      end
      
    # 電話番号
    elsif input_style == :input_style_phonenumber
      if @template.controller.request.mobile_carrier == ActionController::Mobile::SoftBank
        :phonenumber
      else
        :'4'
      end

    # url
    elsif input_style == :input_style_url
      if @template.controller.request.mobile_carrier == ActionController::Mobile::SoftBank
        :url
      else
        :'3'
      end
    end
  end
end

ソフトバンクの入力モードの指定が一番多いので、それを基準にして、ドコモとAUマッピング
requestオブジェクトをどうやって参照すれば良いのかちょい悩んだが、@templateインスタンス経由でアクセスできた。これは素直に本読めば普通に書いてある事だった。

とりあえず、このあとはビューで、下記のようにして呼び出し

<% form_for :signup, :url => { :action => :signup }, :builder => MobileTagBuilder do |form| %>
<%= form.text_field :email, :size => 20, :maxlength => 255, :mobile_input_style => :input_style_alphabet %>

これが正しいやり方なのかどうか分からないけど、とりあえず動いているみたい。絶対ruby的な書き方じゃないだろうし…