Your SlideShare is downloading. ×
ActiveResourceが面白すぎる件
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Saving this for later?

Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime - even offline.

Text the download link to your phone

Standard text messaging rates apply

ActiveResourceが面白すぎる件

5,444
views

Published on

published in RubyKansai #34

published in RubyKansai #34


0 Comments
5 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
5,444
On Slideshare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
9
Comments
0
Likes
5
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. ActiveResourceが面白すぎる件
    松本 一輝
    Lang-8,inc.
    http://d.hatena.ne.jp/kazuk_i
  • 2. 自己紹介
    • Lang-8
    • 3. Ruby、RoR暦
    • 4. RoRに手を出した経緯
  • Lang-8での利用
  • 5. ARes概要
    [ARes]
    Person.find(1)
    [Net::HTTP]
    GET /people/1.json
    #=> #<Person:0xb7ad7368 @attributes={“name”=>”kazuki”...}...>
    [AC]
    HTTP/1.x 200 OK
    {“name”:”kazuki”
    “age”:25
    “...”:”...”}
    • Rails2.0で導入
    • 6. AResはクライアントを担当
    • 7. ActionController(AC)はサーバを担当
    ※今回はクライアント(ARes)の話に限定
  • 8. 規約
    • RESTful
    • 9. レスポンスはXML又はJSONで表現
    • 10. レスポンスのステータスはHTTP status codeで表現
    • 11. ページャは邪道→ サポート外
  • 使用例
    class Journal < ActiveResource::Base
    self.site = "http://foo.com"
    self.format = :json
    end
    Journal.find(119515, :params => {:with_comments => 1})
    #=> will return reply from
    http://foo.com/journals/119515.json?with_comments=1
  • 12. 変化自由な変態性
    AResではARと違い、レスポンスデータが入れ子の場合がある。
    => その場で動的にクラスを作って対応
    {“id”:119515,
    “user_id”:39691
    “subject”:”foo”
    “body”:”bar”
    “comments”:[ {“user_id”:23311,“body”:”zoo”...}, ...]}
    #<Journal:0xb7b1a6f4 @prefix_options={}, @attributes={"user_id”=>39691,”subject”=>”foo”,”body”=>”bar”,”comments”=>[#<Journal::Comment:0xb7b18480 @prefix_options={}, @attributes={"user_id"=>23311, ...]}>, … ]}>
    Singularize
    +
    Camelize
    Journal.find(:last).comments.foo.bar ・・・
     などと辿っていくことができる。
  • 13. 物足りないところ
    • URL pathの自由度
    • 14. Pagination
    • 15. JSONパーサの切り替え
    • 16. 透過キャッシュ機構
  • URL pathの自由度
    • collection_path
    (例) http://foo.com/journals.json
    => ARでいうところの、:all スコープ
    => GET, POSTの操作対象
    • element_path
    (例) http://foo.com/journals/119515.json
    => GET, PUT, DELETE の操作対象
  • 17. Pagination
    • WAN越しなので、データ量の制限が不可欠
     しかし・・・
    The paginator produces horribly unscalable code which will bring your server to a halt. – rabble
  • 18. (参考) non-Rails way APIs
    • 世間のREST APIは、Rails wayではない場合が多い・・・
    例:OpenSocial v0.8 - Activity Resourceの複数取得
    http://foo.com/activities/{guid}/@friends
    #=>
    {“startIndex”: 0,
    “totalResults”: 20,
    “itemsPerPage”: 3,
    “entry”: [
    { “id”: “http://...”
    “title”: “...” },
    {
    } …
    }
  • 19. JSONパーサの切り替え
    プロファイリング:
    100.times { Journal.find(119515, :params => {:with_comments => 1}) }
    (※JournalはActiveResource::Baseの継承クラス)
    Simple-jsonを使った場合:
    0.02 920.39 0.17 100 1.70 168.20 ActiveResource::Connection#request
    0.03 915.76 0.27 100 2.70 8259.20 JsonParser#parse
    0.01 929.72 0.05 100 0.50 605.40 Journal#instantiate_record
    json/extを使った場合:
    0.10 100.00 0.11 100 1.10 166.70 ActiveResource::Connection#request
    0.04 105.45 0.04 100 0.40 6.20 JSON.parse
    0.03 106.76 0.03 100 0.30 605.90 Journal#instantiate_record
  • 20. JSONパーサの切り替え
    (ms)
  • 21. 透過キャッシュ機構
    • Web越しのAPICallは遅延大
     => Call回数をなるべく抑えたい
    • キャッシュの破棄も自動でやってほしい
     => expired timeに頼らない実装が望ましい
    => キャッシュデータのキーは URLにする
     => キャッシュ破棄時には、URLの階層構造を上手く活用する。
    (例) PUT /journals/119515 によって
    GET /journals/119515 のキャッシュだけではなく、
    GET /journals のキャッシュも破棄する。
  • 22. Monkeys
    ActiveResource::Baseにmonkey-patchを当てる。
    想定API: Lang-8 backend API
    GET /journals (複数結果の場合)
    #=>
    {“from”: 0,
    “totalnum”: 20,
    “size”: 3,
    “list”: [
    { “id”: “1”,
    “subject”: “...” },
    {
    } …
    }
    GET /journals/1 (単一結果の場合)
    #=>
    {
    “id”: “1”,
    “subject”: “...” ,
    ...
    }
  • 23. MonkeyPatch一例(その1)
    module ActiveResource
    class Base
    class << self
    def find(*args)
    find_with_response(*args)[0]
    end
    def find_with_response(*args)
    scope = args.slice!(0)
    options = args.slice!(0) || {}
    case scope
    when :all then
    list, body = find_every(options)
    return list, body
    when :first then
    list, body = find_every(options)
    return list.shift, body
    when :last then
    list, body = find_every(options)
    return list.pop, body
    when :one then
    record, body = find_one(options)
    return record, body
    else
    record, body = find_single(scope, options)
    return record, body
    end
    end
    end
    end
    • ActiveResource::Base.find_with_response を追加
     ※レスポンスを生Hashでも返し、
      Pagerの実装に用いる。
    • ActiveResource::Base.find は find_with_responseを利用するよう書き換え。
     
    list, raw_data = Journal.find_with_response(:all)
    raw_data[:totalnum]
    #=> 20
  • 24. MonkeyPatch一例(その2)
    require 'json/ext'
    module ActiveResource
    class Connection
    def get(path, headers = {})
    JSON.parse(request(:get, path, build_request_headers(headers, :get)).body)
    end
    end
    end
    • ActiveSupportではなく、json/extを利用してJSONのパースを行う
  • MonkeyPatch一例(その3)
    class Journal < ActiveResource::Base
    self.site = "http://foo.com"
    self.format = :json
    class << self
    def element_path(id, prefix_options = {}, query_options = nil)
    prefix_options, query_options = split_options(prefix_options) if query_options.nil?
    "/journals/#{id}#{query_string(query_options)}"
    end
    def collection_path(prefix_options = {}, query_options = nil)
    prefix_options, query_options = split_options(prefix_options) if query_options.nil?
    "/journals#{query_string(query_options)}"
    end
    def instantiate_collection(body, prefix_options = {})
    return body['container']['list'].collect! { |record| instantiate_record({'container' => record},
    prefix_options)[0] }, body
    end
    def instantiate_record(body, prefix_options = {})
    record = new(body['container']).tap do |resource|
    resource.prefix_options = prefix_options
    end
    return record, body
    end
    end
    end
    (※赤字部分がAPI依存箇所)
  • 25. AResまとめ
    • AResでCRUDを使いこなすにはMonkeyPatch必要
    • 26. 本格利用にはNative CのJSONパーサ必須
    => json/extのto_json問題
    • 透過キャッシュもあったりすると、いい感じ。
    • 27. それでもAResは結構重い(CPUを食う)
    • 28. Cloud Computingと相性がよいかも。
  • おしまい