ActiveResourceが面白すぎる件

6,270 views
6,057 views

Published on

published in RubyKansai #34

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

No Downloads
Views
Total views
6,270
On SlideShare
0
From Embeds
0
Number of Embeds
242
Actions
Shares
0
Downloads
9
Comments
0
Likes
5
Embeds 0
No embeds

No notes for slide

ActiveResourceが面白すぎる件

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

×