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を使っています。