Kernel.#openの例外処理

ruby2.0

特に理由がないのでmechanizeは使わずopenを使ってスクレイピングしてます。歌丸です。

コード

これは記事一覧ページの存在を確認をするためのメソッド。

  private
  def not_found_index_page?(_error_count = 0)
    is_error_under_5 = ->{ _error_count += 1; _error_count < 5 }

    begin
      open(current_index_page_url)
      return false
    rescue Timeout::Error
      retry if is_error_under_5.call
    rescue SocketError # ホストがないときとか
    rescue OpenURI::HTTPError => e
      case e.io.status[0]
      when /5../
        retry if is_error_under_5.call
      when /4../
      end
    rescue Exception => e
      puts e
    end

    return true
  end
テスト

スタブぐぐるの結構大変だった。

  describe "#not_found_index_page?" do
    let(:lh){ Lifehacker.new('business') }
    subject { lh.send('not_found_index_page?') }
    context "openメソッドが正常終了する時" do
      before { lh.stub(:open).and_return("") }
      it { should be_false }
    end
    context "例外が発生するとき" do
      describe "Timeout::Error" do
        let(:e) { Timeout::Error }
        before { lh.stub(:open).and_raise(e) }
        it { should be_true }
      end
      describe "SocketError" do
        let(:e) { SocketError }
        before { lh.stub(:open).and_raise(e) }
        it { should be_true }
      end
      describe "OpenURI::HTTPError" do
        let(:io) { double('io') }
        describe "5XX" do
          describe "ずっと5XXが返ってくる時" do
            before do
              io.stub_chain(:status,:[]).with(0).and_return('502')
              lh.stub(:open).and_raise(OpenURI::HTTPError.new('', io))
            end
            it { should be_true }
          end
          describe "最初だけ5XXで以降は正常になること" do
            before do
              pending "例外なげたら次は文字列を返したい"
              io.stub_chain(:status,:[]).with(0).and_return('509')
              lh.stub(:open).and_raise(OpenURI::HTTPError.new('', io)).and_return("") # できてない
            end
            it { should be_true }
          end
        end
        describe "4XX" do
          before do
            io.stub_chain(:status,:[]).with(0).and_return('402')
            lh.stub(:open).and_raise(OpenURI::HTTPError.new('', io))
          end
          it { should be_true }
        end
      end
    end
  end

スタブ、and_returnメソッドを使えば同じ型に限って、違う値を返せるけど、
例外と普通の型を交互に投げたい時ってどうするんだろう。

Jpmobileで作ったスマホサイトをスマホから見た時、PCビューを表示するリンクを作る

jpmobile (4.0.0)
rails (4.0.0)
ruby2.0

作るもの

スマホからスマホ対応サイトにアクセスした時に、『PC版を表示する』というリンクがあって、
それをクリックするとスマホからPC版を閲覧ができる機能を作ってみる。

ロジック

特定のクッキーをもっていれば、Jpmobileのビュー自動振り分け機能を無効にしてPCビューを表示する感じ。

実装

before_actionで自動振り分け機能を無効化する

rails4なのでconcernを使ってみる。
app/controllers/concerns/jpmobile.rb

module Concerns
  module Jpmobile
    extend ActiveSupport::Concern

    included do
      before_action :force_pc_view
      before_filter :disable_mobile_view_if_tablet
    end

    # クッキーの中を見てスマホでPCビューを表示する
    def force_pc_view
      if cookies[:force_pc_view] == 't'
        disable_mobile_view! # 自動振り分け無効
      end
    end

    # タブレットは常にPCビューを表示する
    def disable_mobile_view_if_tablet
      if request.mobile && request.mobile.tablet?
        disable_mobile_view!
      end
    end

  end
end

作ったmoduleを取り込む。

class ApplicationController < ActionController::Base
  include Jpmobile::ViewSelector # 自動振り分けする
  include Concerns::Jpmobile      # 追加
end
ルーティング
get '/toggle_pc_view' => 'home#toggle_pc_view', as: :toggle_pc_view
toggle_pc_viewアクションを作る
class HomeController < ApplicationController
  def toggle_pc_view
    if cookies[:force_pc_view] == 't'
      cookies.delete(:force_pc_view)
    else
      cookies[:force_pc_view] = 't'
    end

    redirect_to params['current_path']
  end
end
スマホテンプレートに表示する『リンク』を作る

app/views/layouts/application_smart_phone.html.erbのフッターあたりへ。

 <%= link_to 'PC版の表示', toggle_pc_view_path(current_path: request.path) %>
PCビューを見ている時にスマホビューへ誘導するリンクを作る

app/views/shared/smart_phone/_toggle_pc_view_link.html.erb

<% if request.mobile && request.mobile.tablet? %>
  <%# なに表示しない %>
<% else %>
  <% if request.smart_phone? %>
    <%= link_to 'スマートフォン版の表示', toggle_pc_view_path(current_path: request.path) %>
  <% end %>
<% end %>

このパーシャルをPC用テンプレートに貼る。
app/views/layouts/application.html.erb

...
  <%= render 'shared/smart_phone/toggle_pc_view_link' %>     # 追加
        <%= yield %>
  <%= render 'shared/smart_phone/toggle_pc_view_link' %>  # 追加
...

おしまい。