RailsのpaginationでFlash(AS3)にデータを渡す。

webアプリケーションでは、大量のデータをViewに表示する際に、画面が長くなりすぎないよう、またレスポンス速度向上のため、表示を小分けする事があります。googleの検索結果のような、n件〜x件までを表示し「次へ」「前へ」リンクがある画面がそれです。Railsにはpaginationという機能が実装されていて*1、この小分けが非常に簡単に行えます。データを表示したいとき、railsで既に検索ロジックが実装されている場合、これを利用して検索結果をかっこ良く表示できたら楽しいですね。今回はとりあえず読み込みまでを作ってみる事にします。
データベースにはbooksテーブルが作られていて、id,名前,著者(訳者)の名前がレコードとして記録されているとします。こんな感じです。




このbooksは僕のbooklogからリストをコピペして、ActiveRecordでまるまるレコードを作成したものです。一応コードは以下のような感じ。コピペしたリストはbook_dataという名前で保存してあり、それを読み込んでいます。

#ruby 1.8.6 (2007-09-23 patchlevel 110) [i686-darwin8.11.1]
require "rubygems"
require 'active_record'

ActiveRecord::Base.establish_connection(
  :adapter    =>      'mysql',
  :host       =>      'localhost',
  :username   =>      'root',
  :password   =>      '',
  :database   =>      'test',
  :socket     =>      '/tmp/mysql.sock',
  :encoding   =>      'utf8'
)
 
class Book < ActiveRecord::Base
end

open("book_data") {|file|
  while l = file.gets
    line = l.split(/\//)
    book = Book.new(:name => line[0],:author => line[1])
    redo unless book.save
  end
}

つぎに、デ ータベースからbooksを読み込んでpaginateするrailsのコントローラですが、データをAS3で読み込むにはXMLで出力するべきでしょう。AS3側ではE4Xが使えるからです。paginateは:per_pageで指定された個数分レコードを取得しますが、実はクエリのpage=nを見て、page[n]を呼び出しています、paginateで取得したpageはArrayぽく振る舞うようです。80件のレコードを持つテーブルから10件ずつ取得している場合、8ページになりますが、試しに9ページ目を取得するようにクエリを投げると1ページ目が取得されます。
この例では

http://localhost:3000/book/list?page=n

によって指定されたページのレスポンスが返ります。ここでは、AS3側でこのurlにnを増やしつつリクエストを投げます。
しかし、nがいくつになるまで、すなわち何ページまで取得すればいいのかが分かりません。
仕方ないのでxmlファイルに最大ページがいくつであるかを書いておく事にします。

class BookController < ApplicationController
  def list
    page, books = paginate(:books, :per_page => 10)
    #xmlのヘッダ
    xmlhead = %(<?xml version="1.0" encoding="UTF-8"?>)
    (xml ||=[]) << xmlhead
    
    xml << "<root>"
      #総ページ数の情報
      xml << "<info>"
      xml << "<pageMax>"+page.last.number.to_s+"</pageMax>"
      xml << "</info>"
      #to_xmlメソッドで自動的に付け足されるヘッダを削除
      xml << books.to_xml.gsub(xmlhead,"")
    xml << "</root>"

    render :xml => xml.join("\n")
  end
end

こんな感じに出力されました。




あとはas3で取得するだけ。今回はTimerを使って自動で次のページを取得しています。

package{
  import flash.display.Sprite;
  import flash.text.TextField;
  import flash.net.URLLoader;
  import flash.net.URLRequest;
  import flash.utils.Timer;
  import flash.events.*;
  
  public class PaginateReader extends Sprite {
    private const targetURL:String = "http://localhost:3000/book/list?page=";
    private const loadPageInterval:uint = 1000;
    private var bookIndex:uint = 0;
    private var pageContainer:Array = new Array();
    
    function PaginateReader(){    
      var ULoader:URLLoader = new URLLoader();
      var URequest:URLRequest = new URLRequest();
      var pageIndex:uint = 2;
      /*1ページ目のXMLを取得し終わったら、最大ページ分loadPage呼び出し*/
      ULoader.addEventListener(Event.COMPLETE, 
        function():void{
          var page:XML = new XML(ULoader.data);
          var loadTimer:Timer = new Timer(loadPageInterval, (page..info.pageMax-1));
          loadTimer.addEventListener(TimerEvent.TIMER,
            function():void {
              loadPage(pageIndex++);
            }
          );
          loadTimer.start();
          createPage(page);
          pageContainer[1] = page; //ページのXMLをコンテナに格納
        }
      );
      URequest.url = targetURL+"1";
      ULoader.load(URequest);
    }
    public function loadPage(index:uint):void{
      var pageLoader:URLLoader = new URLLoader();
      var pageRequest:URLRequest = new URLRequest();
      var pageURL = targetURL + index;
      pageLoader.addEventListener(Event.COMPLETE,
        function():void{
          var page:XML = new XML(pageLoader.data);
          pageContainer[index] = page; //ページのXMLをコンテナに格納
          createPage(page);  
        }
      );
      pageRequest.url = pageURL;
      pageLoader.load(pageRequest);
    };
    public function createPage(page:XML):void{
      for(var i:uint=0; i<page..books.book.length(); i++){ 
        createBook(bookIndex, page..books.book[i].name, page..books.book[i].author);
        bookIndex++;
      }
    };
    public function createBook(index:uint, name:String, author:String):void{
      trace((index+1)+":"+name+"/"+author);
    };
  }
}

とりあえずcreateBookで名前と著者名をtraceしていますが、ここで別Spriteなどを作ってaddChildすればTweenerなどでかっこ良く表示できるという事です。この例では1秒ごとに次ページを読み込んでいますが、レスポンス速度に合わせた時間にするといい感じです。

*1:rails2.0では標準で使えません。ここではrails1.2.5を使っています。