場当たり的にJpmobileをRails2.3に対応してみる

Jpmobile

  • 携帯電話の識別
  • 携帯電話viewの自動振分け

というところについて

環境は

リクエストの拡張

Jpmobileはリクエストを拡張して、各リクエストからどのキャリア/機種であるのかを分かるようにしている
拡張しているのは ActionController::AbstractRequest だけれども
Rails2.3.2ではこのクラスはなくなってしまっている。

Rails2.2.2のrequest.class.ancestors

  ActionController::CgiRequest
  ActionController::AbstractRequest
  ActiveSupport::Memoizable::Freezable
  Object
  ・・・

Rails2.3.2のrequest.class.ancestors

  ActionController::Request
  Rack::Request
  Object
  ・・・


というわけで、リクエスト拡張のクラスをRack::Requestに変更する


jpmobile/lib/jpmobile/hook_abstract_request.rb

require 'action_pack'
require 'jpmobile/request_with_mobile'

if ::ActionPack::VERSION::MAJOR >=2 and ::ActionPack::VERSION::MINOR >= 3
  class Rack::Request
    include Jpmobile::RequestWithMobile
  end 
else 
  class ActionController::AbstractRequest
    include Jpmobile::RequestWithMobile
  end 
end

ビューの拡張

Jpmobileはビューを拡張して、/のビューをキャリア別のビュー/_(mobile|docomo|au|softbank|willcom|emobile)に自動的に振り分けてくれる。
自動振り分けのために拡張しているのは

  • ActionView::Base#_pick_template
  • ActionView::Base#render_partial

だけれども、Rails2.3.2では_pick_templateメソッドがなくなってしまっている。


actionpack-2.2.2/lib/action_view/base.rb

    def render(options = {}, local_assigns = {}, &block) #:nodoc:
      local_assigns ||= {}

      if options.is_a?(String)
        ActiveSupport::Deprecation.warn(
          "Calling render with a string will render a partial from Rails 2.3. " +
          "Change this call to render(:file => '#{options}', :locals => locals_hash)."
        )

        render(:file => options, :locals => local_assigns)
      elsif options == :update
        update_page(&block)
      elsif options.is_a?(Hash)
        options = options.reverse_merge(:locals => {}) 
        if options[:layout]
          _render_with_layout(options, local_assigns, &block)
        elsif options[:file]
          _pick_template(options[:file]).render_template(self, options[:locals])
        elsif options[:partial]
          render_partial(options)
        elsif options[:inline]
          InlineTemplate.new(options[:inline], options[:type]).render(self, options[:locals])        elsif options[:text]
          options[:text]
        end
      end
    end   


actionpack-2.3.2/lib/action_view/base.rb

    def render(options = {}, local_assigns = {}, &block) #:nodoc:
      local_assigns ||= {}

      case options
      when Hash
        options = options.reverse_merge(:locals => {}) 
        if options[:layout]
          _render_with_layout(options, local_assigns, &block)
        elsif options[:file]
          template = self.view_paths.find_template(options[:file], template_format)
          template.render_template(self, options[:locals])
        elsif options[:partial]
          render_partial(options)
        elsif options[:inline]
          InlineTemplate.new(options[:inline], options[:type]).render(self, options[:locals])        elsif options[:text]
          options[:text]
        end
      when :update
        update_page(&block)
      else
        render_partial(:partial => options, :locals => local_assigns)
      end
    end   

templateをとってくるのはActionView::Base#_pick_templateからActionView::PathSet#find_templateがやることになりました。
でもJpmobileの自動ビュー振り分けにはリクエストオブジェクトが必須です。ActionView::PathSetはリクエストオブジェクトをとれません。


(追記)てきとうすぎた・・下記では振り分けはうまくいきません。ActiveController::Baseの default_template_name あたりが拡張ポイントだとおもわれ


なのでActionView::Base#render自体をざっくり拡張してみました。


jpmobile/lib/jpmobile/hook_action_view.rb

class ActionView::Base #:nodoc:
  delegate :default_url_options, :to => :controller unless respond_to?(:default_url_options)
  if ::ActionPack::VERSION::MAJOR >=2 and ::ActionPack::VERSION::MINOR >= 3
    ### Rails 2.3 or higher
    alias render_without_jpmobile render #:nodoc:
    alias render_partial_without_jpmobile render_partial #:nodoc:
    
    def render(options = nil, extra_options = {}, &block)
      if options[:file]
        mobile_path = mobile_template_path(options[:file])
        options[:file]=mobile_path if mobile_path
      end
      render_without_jpmobile(options, extra_options, &block);
    end

    def render_partial(options = {}) #:nodoc:
      case partial_path = options[:partial]
      when String, Symbol, NilClass
        mobile_path = mobile_template_path(partial_path, true)
        options = options.merge(:partial => mobile_path) if mobile_path
      end
      render_partial_without_jpmobile(options)
    end
  if ::ActionPack::VERSION::MAJOR >=2 and ::ActionPack::VERSION::MINOR >= 3
    ### Rails 2.3 or higher
    def template_exists?(template_name)
      self.view_paths.find_template(template_name) ? true : false
    rescue ActionView::MissingTemplate
      false
    end
  elsif ::ActionPack::VERSION::MAJOR >=2 and ::ActionPack::VERSION::MINOR >= 2
    ### Rails 2.2
    def template_exists?(template_name)
      send(:_pick_template_without_jpmobile, template_name) ? true : false
    rescue ActionView::MissingTemplate
      false
    end
  else
    ### Rails 2.1 or lower
    def template_exists?(template_name)
      finder.file_exists?(template_name)
    end
  end


てきとうだ・・
とりあえず動く。
拡張範囲をrenderにしてるのは2.2の拡張からするとかなり大ざっぱなのでどっか影響して動かなくなりそうな気がする。
ActionView::PathSet#find_templateを拡張するのがいいんだろうけどリクエストオブジェクトがとれないのをどうしたらよいのだろう?


あと、JpmobileがRails2.3に全対応するにはtrans_sidをなんとかしないといけないっぽい。


テストを流してみる

Jpmobileのインストールディレクトリでrake test
さっそくエラー。
TestRequestがAbstractRequest継承だ。とりあえず対処。


jpmobile/test/helper.rb

if ::ActionPack::VERSION::MAJOR >=2 and ::ActionPack::VERSION::MINOR >= 3
  module Rack
    class TestRequest < Request
      attr_accessor :user_agent
    end 
  end 
else
  module ActionController
    class TestRequest < AbstractRequest
      attr_accessor :user_agent
    end 
  end 
end


さらにエラー。ActionController::CgiRequestのinitialize(引数2個)がRails2.3では定義されてない。
lib/jpmobile/trans_sid.rb

module ActionController
  class CgiRequest
    alias_method :initialize_without_ext, :initialize
    def initialize(cgi, options = {}) 
      initialize_without_ext(cgi, options)
      ENV['QUERY_STRING'] = query_string
    end 
  end 
end

これもどげんかせんといかん。