Play frameworkでisucon2アプリを書く-4

このエントリはPlay frameworkひとりアドベントカレンダーの19日目です。

今日は /artist/ を実装します。

controllerを移植

例によって元コードを見ます。

get '/artist/:artistid' => [qw(recent_sold)] => sub {
    my ($self, $c) = @_;
    my $artist = $self->dbh->select_row(
        'SELECT id, name FROM artist WHERE id = ? LIMIT 1',
        $c->args->{artistid},
    );
    my $tickets = $self->dbh->select_all(
        'SELECT id, name FROM ticket WHERE artist_id = ? ORDER BY id',
        $artist->{id},
    );
    for my $ticket (@$tickets) {
        my $count = $self->dbh->select_one(
            'SELECT COUNT(*) FROM variation
             INNER JOIN stock ON stock.variation_id = variation.id
             WHERE variation.ticket_id = ? AND stock.order_id IS NULL',
            $ticket->{id},
        );
        $ticket->{count} = $count;
    }
    $c->render('artist.tx', {
        artist  => $artist,
        tickets => $tickets,
    });
};
  • artist.idでartistからartistを取得
  • artis.idでticketからticketを取得
  • ticketごとにカウントを取得

さて、ticketごとのカウントをviewにどう渡すか困ってしまいました。。
とりあえず動けばいいので、力技ですがHashMapで渡すことにしましょう。

controllerとviewのあいだのインターフェースが決まったところでコーディングを進めましょう。

modelを作る

ticketのmodelを作りましょう。

CREATE TABLE IF NOT EXISTS isucon2.ticket (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255) NOT NULL,
  `artist_id` INT UNSIGNED NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

繰り返しですが、下記に注意しましょう。

  • /app/models/ 直下に作る
  • play.db.ebean.Model を継承する
  • @Entity アノテーションをつける
  • finder を定義する
package models;

import javax.persistence.Entity;
import javax.persistence.Id;

import play.db.ebean.Model;

@Entity
public class Ticket extends Model {

	@Id
	public Integer id;
	public String name;
	public Integer artistId;

	public static Finder find = new Finder(
			Integer.class, Ticket.class);
}

view

artist.scala.html を書きましょう。

まずはこぴぺしてから、渡すデータ(1行目)とくるくる( @for )を書きましょう。

@(artist :Artist, ticketMaps :List[HashMap[String, String]])
(略)
    <div id="content">
<h2>@artist.name</h2>
<ul>
@for(ticketMap &lt;- ticketMaps){
<li class="ticket">
  <a>@ticketMap("name")</a>残り<span class="count">@ticketMap("count")</span>枚
</li>
}
</ul>
    </div>
(略)

controller

本邦初登場。 /conf/routes でパラメータを使ってみましょう。

GET     /artist/:artistId           controllers.Application.artist(artistId:Integer)
	public static Result artist(Integer artistId) {
		return TODO;
	}

この状態でとりあえずブラウザで http://localhost:9000/artist/1 とかにアクセスすると、TODOの画面が出ます。
これでroutesが間違ってないか確認できるわけです。
TODO って便利!

ここからデータをごにょりましょう。

まずは Artist 。これは簡単。

		Artist artist = Artist.find.byId(artistId);

これだけ。

次に Ticket をもとに List&lt;HashMap&gt; をつくりましょ。これはちょっとタイヘンです。

find.byId は簡単なのでokですね。

SQLを直接実行するのは、今回は参照クエリなのでEBeanのcreateSqlQueryでやりましょう。
parameter bindingが使えるので積極的に使いましょう。当然使うよね的な感じで。

	public static Result artist(Integer artistId) {
		Artist artist = Artist.find.byId(artistId);

		List tickets = Ticket.find.where().eq("artist_id", artistId)
				.orderBy("id").findList();
		List<HashMap> ticketMaps = new ArrayList<HashMap>(
				tickets.size());
		SqlQuery query = Ebean
				.createSqlQuery("SELECT COUNT(*) as cnt FROM variation"
						+ " INNER JOIN stock ON stock.variation_id = variation.id"
						+ " WHERE variation.ticket_id = :ticketId AND stock.order_id IS NULL");
		for (Ticket ticket : tickets) {
			HashMap ticketMap = new HashMap();
			ticketMap.put("id", ticket.id.toString());
			ticketMap.put("name", ticket.name);
			List count = query.setParameter("ticketId", ticket.id)
					.findList();
			ticketMap.put("count", count.get(0).getInteger("cnt").toString());
			ticketMaps.add(ticketMap);
		}

		return ok(views.html.artist.render(artist, ticketMaps));
	}

ここまでできたら再度ブラウザでアクセスしてみましょう。
もしデータが空だったら、initial_data.sqlを使ってデータを再投入してくださいね。

スクリーンショット 2012-12-18 21.59.02

デターヽ(`▽´)/


See also