このエントリは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 <- 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<HashMap>
をつくりましょ。これはちょっとタイヘンです。
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を使ってデータを再投入してくださいね。
デターヽ(`▽´)/