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

MacVim KaoriYaを使い始めたところ文字が見えなくて困った

MacVim KaoriYaを始めました。
今まではitermの上でvimを使ってました。

MacVimを立ち上げたところ、グレー背景に文字が見えない!
f:id:tohtas:20100623222600p:image

なにか色系の設定がおかしなことになってるんだろうなーといろいろ試したところ、.vimrcにcolorschemeの設定を書いていて、.gvimrcに書いてないときにこうなってしまうことが分かりました。

こういうの書いた~/.gvimrcをつくりました。

colorscheme railscasts
set showtabline=2  " タブを常に表示
set imdisable " IMを無効化
set transparency=5 " 透明度を指定
set antialias
set guifont=Monaco:h14
set columns=120
set lines=50

Amazon SimpleDBのConsistentReadを指定してみる

SimpleRecordというSimpleDB専用のActiveRecordクローンを試しているときに登録したはずのデータが取得できない現象が頻発しました。

POSTでデータ登録 -> Redirect GETで登録したデータを取得して表示しようとしたら、データが取得できてない!という流れです。

これはEventually Consistentが働いている現象なんだなーと思いました。(取得するデータが最新の状態である保証がない)

SimpleDBは今年の2月に取得するデータが最新の状態であることを保証するオプション「ConsistentRead」が追加されていました。

が、SimpleRecordもその中で使われているawsもConsistentReadは未対応のようです。
なので無理矢理ConsistentReadを使うように変えてみました。
aws-2.3.9/lib/awsbase/right_awsbase.rb

        def self.sign_request_v2(aws_secret_access_key, service_hash, http_verb, host, uri)
            fix_service_params(service_hash, '2')
            # select a signing method (make an old openssl working with sha1)
            # make 'HmacSHA256' to be a default one
            service_hash['SignatureMethod'] = 'HmacSHA256' unless ['HmacSHA256', 'HmacSHA1'].include?(service_hash['SignatureMethod'])
            service_hash['SignatureMethod'] = 'HmacSHA1' unless @@digest256

            #FORCE ConsistentRead
            service_hash['ConsistentRead'] = 'true'

            # select a digest
            digest = (service_hash['SignatureMethod'] == 'HmacSHA256' ? @@digest256 : @@digest1)
            # form string to sign
            canonical_string = service_hash.keys.sort.map do |key|
                "#{amz_escape(key)}=#{amz_escape(service_hash[key])}"
            end.join('&')

            string_to_sign = "#{http_verb.to_s.upcase}\n#{host.downcase}\n#{uri}\n#{canonical_string}"
            # sign the string
            signature = escape_sig(Base64.encode64(OpenSSL::HMAC.digest(digest, aws_secret_access_key, string_to_sign)).strip)
            ret = "#{canonical_string}&Signature=#{signature}"
#            puts 'full=' + ret.inspect
            ret
        end


この

service_hash['ConsistentRead'] = 'true'

一行を追加です。


これでConsistentReadできているようです(たぶん)
クエリーごとに指定できるようなのがほんとはええわな。

Eventually Consistentなデータストアを使う開発では気をつけよう。