youRoomのAPIを使ってみた

youRoomのAPIを使ってなにかしてみたいと思いました。
APIを使うには申し込みが必要です。申し込みはここからできました。

youRoom APIをベータユーザ向けに限定公開します!

そうすると、Consumer KeyとConsumer Secretをすぐに発行してもらえます。

発行してもらったConsumer KeyとConsumer SecretをもとにAPIをたたくために必要なアクセストークンを取ってみます。
こちらの記事を参考にしてます。
Sinatra と OAuth を使って Twitter のタイムラインを取得してみた

こんな環境で試しました。

# ruby -v
ruby 1.9.2p0 (2010-08-18 revision 29036) [i386-darwin9.8.0]
# gem list

*** LOCAL GEMS ***

oauth (0.4.3)
rack (1.2.1)
sinatra (1.0)

youroom.rb

require 'rubygems'
require 'sinatra'
require 'oauth'

helpers do
  include Rack::Utils
  alias_method :h, :escape_html
end

enable :sessions
configure do
#  use Rack::Session::Cookie, :secret=> Digest::SHA1.hexdigest(rand.to_s)
  KEY = "Consumer Key"
  SECRET = "Consumer Secret"
end

template :layout do
  <<-EOS
<html>
<head><title>OAuth Example with Sinatra</title></head>
<body>
<%= yield %>
</body>
</html>
EOS
end

def base_url
  default_port = (request.scheme == "http") ? 80 : 443
  port = (request.port == default_port) ? "" : ":#{request.port.to_s}"
  "#{request.scheme}://#{request.host}#{port}"
end

def oauth_consumer
  OAuth::Consumer.new(KEY, SECRET, :site => "http://youroom.in")
end

get '/' do
  erb %{ <a href="/request_token">OAuth Login</a> }
end

get '/request_token' do
  puts base_url
  callback_url = "#{base_url}/access_token"
  request_token = oauth_consumer.get_request_token(:oauth_callback => callback_url)
  session[:request_token] = request_token.token
  session[:request_token_secret] = request_token.secret
  redirect request_token.authorize_url
end

get '/access_token' do
  request_token = OAuth::RequestToken.new(
    oauth_consumer, session[:request_token], session[:request_token_secret])
  begin
    @access_token = request_token.get_access_token(
      {},
      :oauth_token => params[:oauth_token],
      :oauth_verifier => params[:oauth_verifier])
  rescue OAuth::Unauthorized => @exception
    return erb %{ oauth failed: <%=h @exception.message %> }
  end
  session[:access_token] = @access_token.token
  session[:access_token_secret] = @access_token.secret
  erb %{
oauth success!
<dl>
<dt>access token</dt>
<dd><%=h @access_token.token %></dd>
<dt>secret</dt>
<dd><%=h @access_token.secret %></dd>
</dl>
}
end

#for 1.9.2
enable :run

KEYとSECRETには発行してもらったConsumer KeyとConsumer Secretを設定します。

起動します。

# ruby youroom.rb 

ブラウザでhttp://localhost:4567にアクセスします。
「OAuth Login」のリンクをクリックします。
youRoomの承認画面が表示されるので、「Allow」をクリックします。
Access TokenとAccess Secretが画面に表示されます。
アクセストークンを取得しました!

irbからアクセストークンを使ってAPIをたたいてみます。

ruby-1.9.2-p0 > require 'rubygems'
ruby-1.9.2-p0 > require 'oauth'
ruby-1.9.2-p0 > require 'json'
ruby-1.9.2-p0 > consumer = OAuth::Consumer.new(<Consumer Key>, <Consumer Secret>, :site => "http://youroom.in")
ruby-1.9.2-p0 > access_token = OAuth::AccessToken.new(consumer, <Access Token>, <Access Secret>)
ruby-1.9.2-p0 > res = access_token.get("https://www.youroom.in/r/<group-param>/all?format=json") #<group-param>のRoomのタイムラインを取得する
ruby-1.9.2-p0 > JSON.parse(res.body)

10個しか発言がとれてない。

pageパラメータを指定して10個ずつ取らないとダメみたいです。こんな感じで

ruby-1.9.2-p0 > res = access_token.get("https://www.youroom.in/r/<group-param>/all?format=json&flat=true&page=3")

いったい全部で何ページあるのかわからない・・

RSpecでApplicationControllerのテストを書く

初めて書くにあたって、なにか特別なことしなきゃいけないんだろうなーと思っていたら、ズバリな紹介がありました。

rescue_action_in_public の RSpec を書く


自分のこの環境では、

use_rails_error_handling!じゃなくてrescue_action_in_public!を使えと警告が出たので従いました。

GmailからIMAPで取ったメールの添付ファイルを取ろうとしたらTMailでエラーになる

環境

Ruby 1.8.7
Rails 2.3.8

#attachmentsでエラーが出る

  tmail = TMail::Mail.parse(mail_data.attr['BODY[]'])
  @read = tmail.attachments #ここ

こんなエラー

undefined local variable or method `jp2CharContext' for #<CharDet::SJISContextAnalysis:0x1e35b38>

RubyForgeこの件は報告されてるけど対応されてないようす。

ワークアラウンド

TMailを使わないこのやり方だとうまくいくみたいです。
Tmail + IMAP + Attachments

モデルのバリデーションのテストをダラダラ書きたくない

RSpecでモデルのバリデーションのテストをダラダラ書かないで済むうまいやり方ってあるんでしょうか?
ダラダラ書かないで済む方法を模索してみました。


spec_helper.rbにこういうのを追加します。ここではバリデーションの種類はverify_hours(入力値が24(時間)以内かチェックする)だけあります。
種類を追加するときは、verify_hoursをまるっとコピーしたメソッドをつくって、valid_values、invalid_valuesの配列にそれぞれ有効値、無効値を書くだけです。

def verify_valid(klass, col_name, value)
  valid_check = Proc.new {
    attributes = @valid_attributes.merge(col_name.to_sym => value)
    obj = klass.new(attributes)
    obj.should be_valid
    obj.should have(0).errors_on(col_name.to_sym)
    obj.should have(0).errors
    obj.save.should be_true
  }
end

def verify_invalid(klass, col_name, value)
  invalid_check = Proc.new {
    attributes = @valid_attributes.merge("#{col_name}" => value)
    obj = klass.new(attributes)
    obj.should be_invalid
    obj.should have(1).errors_on(col_name.to_sym)
    obj.should have(1).errors
    obj.save.should be_false
  }
end

def verify_hours(klass, col_name)
  valid_values = [0, 3.5, 12, 24]
  invalid_values = [nil, "", "hankaku_moji", "全角文字", -1, -2.3, 25]

  valid_values.each{|value| it "#{col_name} 正常(#{value.inspect})", &verify_valid(klass, col_name, value) }
  invalid_values.each{|value| it "#{col_name} 異常(#{value.inspect})", &verify_invalid(klass, col_name, value)}
end


モデルのSpecにはこんな感じで書きます。

before(:each) do
  @valid_attributes = {
    #各項目がvalidになる値を定義しておく
  }
end

describe "target_attributeは1日の時間であること" do
  verify_hours(TargetClass, :target_attribute)
end


出力はこんな感じになります。

TargetClass target_attributeは1日の使用時間であること
- target_attribute 正常(0)
- target_attribute 正常(3.5)
- target_attribute 正常(12)
- target_attribute 正常(24)
- target_attribute 異常(nil)
- target_attribute 異常("")
- target_attribute 異常("hankaku_moji")
- target_attribute 異常("全角文字")
- target_attribute 異常(-1)
- target_attribute 異常(-2.3)
- target_attribute 異常(25)


ちょっと調べてみたところ、accept_values_forというマッチャを追加するよいソリューションがありました。
accept_values_for

describe User do
  subject { User.new(@valid_attributes)}
  it { should accept_values_for(:email, "john@example.com", "lambda@gusiev.com") }
  it { should_not accept_values_for(:email, "invalid", nil, "a@b", "john@.com") }
end

これはいいですね。

integration testではAuthenticatedTestHelperのlogin_asが使えない

RestfulAuthenticationプラグインで認証しているアプリのintegration testを書こうとしました。
プラグインに同梱されているAuthenticatedTestHelperのlogin_asというログインのヘルパメソッドを使ってみたのですが、なぜか認証できてない。

AuthenticatedTestHelperのlogin_asの中をみてみると、ActionController::TestRequestのオブジェクトのセッションに認証対象のモデルのidを入れてました。
でも、integration testはActionController::Integration::Sessionのコンテキストで実行されていて、TestRequest,TestResponseは使ってないんですね、、、このヘルパはfunctional testしか使えなかったのか、、、

とりあえずintegration test向けにヘルパメソッドを書きました。これをintegration testのクラスでincludeします。

  def login_as_for_it(user)
    reset!
    u  = user ? (user.is_a?(User) ? user.id : users(user)) : nil
    get "/login"
    post "/login", :login => u.login, :password => u.password
  end


(追記)インターフェースを変えないほうがいいですね。

  alias login_as_original login_as

  def login_as(user)
    if self.class.superclass == ActionController::IntegrationTest
      #IntegrationTest
      reset!
      u  = user ? (user.is_a?(User) ? user.id : users(user)) : nil
      get "/login"
      post "/login", :login => u.login, :password => u.password
    else
      #FunctionalTest
      login_as_original(user)
    end
  end