Play! - Step By Step Tutorial その2 〜Fixtures〜


Fixtures

Playではエンティティの初期データ作成のために Fixtures という仕組みが提供されている。Rails と同様。test/data.yml に以下の YAML ファイルを用意。

# Test data

User(bob):
    email:          bob@gmail.com
    password:       secret
    fullname:       Bob
    isAdmin:        true
    
User(jeff):
    email:          jeff@gmail.com
    password:       secret
    fullname:       Jeff    
    
Post(firstBobPost):
    title:          About the model layer
    postedAt:       2009-06-14
    author:         bob
    content:        >
                    The model has a central position in a Play! application. It is the domain-specific 
                    representation of the information on which the application operates.
                    
                    Martin fowler defines it as :
                        
                    Responsible for representing concepts of the business, information about the 
                    business situation, and business rules. State that reflects the business situation 
                    is controlled and used here, even though the technical details of storing it are 
                    delegated to the infrastructure. This layer is the heart of business software.

Post(secondBobPost):
    title:          Just a test of YABE
    postedAt:       2009-03-25
    author:         bob
    content:        >
                    Well, it's just a test.
                    
Post(jeffPost):
    title:          The MVC application
    postedAt:       2009-06-06
    author:         jeff
    content:        >
                    A Play! application follows the MVC architectural pattern as applied to the 
                    architecture of the Web.
                    
                    This pattern splits the application into separate layers: the Presentation 
                    layer and the Model layer. The Presentation layer is further split into a 
                    View and a Controller layer.
                    
Comment(c1):
    author:         Guest
    content:        >
                    You are right !
    postedAt:       2009-06-14
    post:           firstBobPost
    
Comment(c2):
    author:         Mike
    content:        >
                    I knew that ...
    postedAt:       2009-06-15
    post:           firstBobPost    
    
Comment(c3):
    author:         Tom
    content:        >
                    This post is useless ?
    postedAt:       2009-04-05
    post:           secondBobPost    

Fixtures の利用

Fixtures は以下で YAML からエンティテイの情報を読み込み永続化できる。

Fixtures.load("data.yml");


/test/BasicTest.javaに以下のテストメソッド追加。

@Test
public void fullTest() {
    Fixtures.load("data.yml");
 
    // Count things
    assertEquals(2, User.count());
    assertEquals(3, Post.count());
    assertEquals(3, Comment.count());
 
    // Try to connect as users
    assertNotNull(User.connect("bob@gmail.com", "secret"));
    assertNotNull(User.connect("jeff@gmail.com", "secret"));
    assertNull(User.connect("jeff@gmail.com", "badpassword"));
    assertNull(User.connect("tom@gmail.com", "secret"));
 
    // Find all bob's posts
    List<Post> bobPosts = Post.find("author.email", "bob@gmail.com").fetch();
    assertEquals(2, bobPosts.size());
 
    // Find all comments related to bob's posts
    List<Comment> bobComments = Comment.find("post.author.email", "bob@gmail.com").fetch();
    assertEquals(3, bobComments.size());
 
    // Find the most recent post
    Post frontPost = Post.find("order by postedAt desc").first();
    assertNotNull(frontPost);
    assertEquals("About the model layer", frontPost.title);
 
    // Check that this post has two comments
    assertEquals(2, frontPost.comments.size());
 
    // Post a new comment
    frontPost.addComment("Jim", "Hello guys");
    assertEquals(3, frontPost.comments.size());
    assertEquals(4, Comment.count());
}

テストケースのデータ準備が省力化できる。テストの実行結果は以下。

起動時の初期データ

Fixtures は起動時の初期データ投入にも利用できる。
/app/Bootstrap.java に以下を作成。

import play.*;
import play.jobs.*;
import play.test.*;
import models.*;
 
@OnApplicationStart
public class Bootstrap extends Job {
    public void doJob() {
        // Check if the database is empty
        if(User.count() == 0) {
            Fixtures.load("initial-data.yml");
        }
    }
}

Play の Job は HTTP リクエストとは別に特定の条件で起動するcron jobとして利用できる。上記は @OnApplicationStart アノテーションによりアプリケーションのスタート時に自動実行される。
Playの起動モードは DEV と PROD モードがあり、DEVモード時には初回のHTTPアクセス時にデータ作成が行われる。PROD モードの場合にはアプリケーションの起動時となる。DEV モードではアクセス時のブラウザ画面にてエラーの原因を表示するため。

テストデータ用のYAMLは/conf/initial-data.yml に、前述のdata.yml の内容で作成。

トップページの作成

/app/controllers/Application.java を変更してトップページのコントローラを作成。

public class Application extends Controller {
    public static void index() {
    	Post frontPost = Post.find("order by postedAt desc").first();
        List<Post> olderPosts = Post.find(
            "order by postedAt desc"
        ).from(1).fetch(10);
        render(frontPost, olderPosts);
    }
}

render の引数として取得した情報を渡している。
これらの情報を表示するページテンプレートを編集 /app/views/Application/index.html

#{extends 'main.html' /}
#{set title:'Home' /}
 
#{if frontPost}
    <div class="post">
        <h2 class="post-title">
            <a href="#">${frontPost.title}</a>
        </h2>
        <div class="post-metadata">
            <span class="post-author">by ${frontPost.author.fullname}</span>
            <span class="post-date">${frontPost.postedAt.format('MMM dd')}</span>
            <span class="post-comments">
                &nbsp;|&nbsp; 
                ${frontPost.comments.size() ?: 'no'} 
                comment${frontPost.comments.size().pluralize()}</a>
                #{if frontPost.comments}
                    , latest by ${frontPost.comments[0].author}
                #{/if}
            </span>
        </div>
        <div class="post-content">
            ${frontPost.content.nl2br()}
        </div>
    </div>
    
    #{if olderPosts.size() > 1}
        <div class="older-posts">    
            <h3>Older posts <span class="from">from this blog</span></h3>
        
            #{list items:olderPosts, as:'oldPost'}
                <div class="post">
                    <h2 class="post-title">
                        <a href="#">${oldPost.title}</a>
                    </h2>
                    <div class="post-metadata">
                        <span class="post-author">
                            by ${oldPost.author.fullname}
                        </span>
                        <span class="post-date">
                            ${oldPost.postedAt.format('dd MMM yy')}
                        </span>
                        <div class="post-comments">
                            ${oldPost.comments.size() ?: 'no'} 
                            comment${oldPost.comments.size().pluralize()}
                            #{if oldPost.comments}
                                - latest by ${oldPost.comments[0].author}
                            #{/if}
                        </div>
                    </div>
                </div>
            #{/list}
        </div>
        
    #{/if}
    
#{/if}
 
#{else}
    <div class="empty">
        There is currently nothing to read here.
    </div>
#{/else}

実行結果は、

Fixtures にて設定したデータが表示されていることが分る。