フォームの作成

http://sites.google.com/site/slim3appengine/getting-started/creating-form

では実際にTweetするフォームを作りましょう。

index.jsp

<%@page pageEncoding="UTF-8" isELIgnored="false"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@taglib prefix="f" uri="http://www.slim3.org/functions"%>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>twitter Index</title>
<link rel="stylesheet" type="text/css" href="/css/global.css" />
</head>
<body>
<p>What are you doing?</p>
<form method="post" action="tweet">
<textarea name="content"></textarea><br />
<input type="submit" value="tweet"/>
</form>
</body>
</html>

http://localhost:8888/twitter/ にアクセスしてください。 "index.jsp" が以下のように表示されるでしょう。


何か入力して送信してください。すると 404 NOT FOUND HTTP ERROR が表示されるでしょう。なぜならまだ /twitter/tweet のコントローラを作成していないからです。では作成してみましょう。

プロジェクトの下にある build.xml をダブルクリックしてください。
Outline view で gen-controller-without-viewタスクを右クリック , Run as > Ant Build(最初に表示されているもの)を選択してください。
antの入力ダイアログで "/twitter/tweet" を入力して OKボタンをクリックしてください。

もしプロジェクトが自動的に更新されなかったら、WindowメニューのPreferences > General > Workspace を選択して、"Refresh automatically" チェックボックスにチェックを入れるか、手動でプロジェクトを更新してください。

"tutorial.controller.twitter.TweetController", "tutorial.controller.twitter.TweetControllerTest" が生成されます。
TweetController は実行すると "/twitter/ に転送されます。なのでJSPファイルを定義する必要はありません。もしJSPファイルを使わないコントローラを生成するのであれば gen-controller-without-view タスクを使用してください。

TweetController.java

package tutorial.controller.twitter;

import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;

public class TweetController extends Controller {

    @Override
    public Navigation run() throws Exception {
        return null;
    }
}

TweetControllerTest.java

package tutorial.controller.twitter;

import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.slim3.tester.ControllerTestCase;

public class TweetControllerTest extends ControllerTestCase {

    @Test
    public void run() throws Exception {
        tester.start("/twitter/tweet");
        TweetController controller = tester.getController();
        assertThat(controller, is(notNullValue()));
        assertThat(tester.isRedirect(), is(false));
        assertThat(tester.getDestinationPath(), is(nullValue()));
    }
}

"TweetControllerTest.java"を実行してください。テストが成功すると思います。
TweetController は実行されると "/twitter/" にリダイレクトされるので、テストケースを以下のように変更してください。

package tutorial.controller.twitter;

import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.slim3.tester.ControllerTestCase;

public class TweetControllerTest extends ControllerTestCase {

    @Test
    public void run() throws Exception {
        tester.start("/twitter/tweet");
        TweetController controller = tester.getController();
        assertThat(controller, is(notNullValue()));
        assertThat(tester.isRedirect(), is(true));
        assertThat(tester.getDestinationPath(), is("/twitter/"));
    }
}

"TweetControllerTest.java"を実行してください。テストが失敗するでしょう。なぜなら TweetController の run メソッドが null を返すからです。コントローラを以下のように変更してみましょう。 
package tutorial.controller.twitter;

import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;

public class TweetController extends Controller {

    @Override
    public Navigation run() {
        return redirect(basePath);
    }
}


この場合、"basePath" は "/twitter/" と等しくなります。"TweetControllerTest.java"を実行してください。テストが成功するでしょう。それでは、tweetされた内容を保存するためにModelを作成してみましょう。

プロジェクトの下にある build.xml をダブルクリックしてください。
Outline viewで gen-modelタスクを右クリックし Run as > Ant Build(最初のアイテム)を選択してください。
antの入力ダイアログに "Tweet" を入力しOKボタンを押してください。

もしプロジェクトが自動的に更新されなかったら、WindowメニューのPreferences > General > Workspace を選択して、"Refresh automatically" チェックボックスにチェックを入れるか、手動でプロジェクトを更新してください。

"tutorial.model.Tweet", "tutorial.model.TweetTest" が生成されます。


Tweet.java

package tutorial.model;

import java.io.Serializable;

import com.google.appengine.api.datastore.Key;

import org.slim3.datastore.Attribute;
import org.slim3.datastore.Model;

@Model(schemaVersion = 1)
public class Tweet implements Serializable {

    private static final long serialVersionUID = 1L;

    @Attribute(primaryKey = true)
    private Key key;

    @Attribute(version = true)
    private Long version;

    /**
     * Returns the key.
     *
     * @return the key
     */
    public Key getKey() {
        return key;
    }

    /**
     * Sets the key.
     *
     * @param key
     *            the key
     */
    public void setKey(Key key) {
        this.key = key;
    }

    /**
     * Returns the version.
     *
     * @return the version
     */
    public Long getVersion() {
        return version;
    }

    /**
     * Sets the version.
     *
     * @param version
     *            the version
     */
    public void setVersion(Long version) {
        this.version = version;
    }
}

TweetTest.java

package tutorial.model;

import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

public class TweetTest {

    private Tweet model = new Tweet();

    @Test
    public void test() throws Exception {
        assertThat(model, is(notNullValue()));
    }
}

"TweetTest.java"を実行してください。テストが成功すると思います。
さあ中身を追加してみましょう。createdDateプロパティを以下のようにTweetモデルに追加します。

Tweet.java

package tutorial.model;

import java.io.Serializable;
import java.util.Date;

import com.google.appengine.api.datastore.Key;

import org.slim3.datastore.Attribute;
import org.slim3.datastore.Model;

@Model(schemaVersion = 1)
public class Tweet implements Serializable {

    private static final long serialVersionUID = 1L;

    @Attribute(primaryKey = true)
    private Key key;

    @Attribute(version = true)
    private Long version;
    
    private String content;
    
    private Date createdDate = new Date();

    /**
     * Returns the key.
     *
     * @return the key
     */
    public Key getKey() {
        return key;
    }

    /**
     * Sets the key.
     *
     * @param key
     *            the key
     */
    public void setKey(Key key) {
        this.key = key;
    }

    /**
     * Returns the version.
     *
     * @return the version
     */
    public Long getVersion() {
        return version;
    }

    /**
     * Sets the version.
     *
     * @param version
     *            the version
     */
    public void setVersion(Long version) {
        this.version = version;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Date getCreatedDate() {
        return createdDate;
    }

    public void setCreatedDate(Date createdDate) {
        this.createdDate = createdDate;
    }
}

"TweetTest.java"を実行してください。テストが成功するでしょう。
これで Tweetモデルの準備ができました。

Modelはデータモデルとサービスで構成されます。データモデルの主な役割はデータを保存することです。サービスの主な役割はユースケースを実行することです。もちろんデータモデルでもデータで使用するいくつかのロジックを持ちます。

それではTweetモデルをデータストアに保存するためにサービスを作成しましょう。

プロジェクトの下にある build.xml をダブルクリックします。
Outline viewで gen-serviceタスクを右クリックして、Run as > Ant Build(最初のアイテム)を選択してください。
Antの入力ダイアログに"TwitterService"と入力してOKボタンを押してください。

もしプロジェクトが自動的に更新されなかったら、WindowメニューのPreferences > General > Workspace を選択して、"Refresh automatically" チェックボックスにチェックを入れるか、手動でプロジェクトを更新してください。

"tutorial.service.TwitterService", "tutorial.service.TwitterServiceTest" が生成されます。

TwitterService.java

package tutorial.service;


public class TwitterService {

}

TwitterServiceTest.java

package tutorial.service;

import org.slim3.tester.AppEngineTestCase;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

public class TwitterServiceTest extends AppEngineTestCase {

    private TwitterService service = new TwitterService();

    @Test
    public void test() throws Exception {
        assertThat(service, is(notNullValue()));
    }
}

"TwitterServiceTest.java"を実行します。テストが成功するでしょう。
つぶやいた内容をデータストアに保存するメソッドを実装したいと思います。まず最初にテストを以下のように書きます。

TwitterServiceTest.java

package tutorial.service;

import java.util.HashMap;
import java.util.Map;

import org.slim3.datastore.Datastore;
import org.slim3.tester.AppEngineTestCase;
import org.junit.Test;

import tutorial.model.Tweet;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

public class TwitterServiceTest extends AppEngineTestCase {

    private TwitterService service = new TwitterService();

    @Test
    public void test() throws Exception {
        assertThat(service, is(notNullValue()));
    }
    
    @Test
    public void tweet() throws Exception {
        Map<String, Object> input = new HashMap<String, Object>();
        input.put("content", "Hello");
        Tweet tweeted = service.tweet(input);
        assertThat(tweeted, is(notNullValue()));
        Tweet stored = Datastore.get(Tweet.class, tweeted.getKey());
        assertThat(stored.getContent(), is("Hello"));
    }
}


KeyからModelを取得します。Datastore.get(Class modelClass, Key key)を使用してください。
詳細はキーによるモデルの取得を参照してください。
コンパイルエラーが発生します。"The method tweet is undefined for the type TwitterService"というエラーが出るので、TwitterServiceを作成しましょう。

TwitterService.java

package tutorial.service;

import java.util.Map;

import org.slim3.datastore.Datastore;
import org.slim3.util.BeanUtil;

import com.google.appengine.api.datastore.Transaction;

import tutorial.model.Tweet;


public class TwitterService {

    public Tweet tweet(Map<String, Object> input) {
        Tweet tweet = new Tweet();
        BeanUtil.copy(input, tweet);
        Transaction tx = Datastore.beginTransaction();
        Datastore.put(tweet);
        tx.commit();
        return tweet;
    }
}
BeanUtil は全ての入力データをModelにある同じ名前のプロパティに値をコピーします。もしプロパティの型が違っていても値は変換されます。
Modelをデータストアに保存する場合は、Datastore.put(Object model)を使用してください。
詳細はモデルの作成を参照してください。
もしトランザクションの中で例外が起きた場合、Slim3は自動的にロールバックします。

コンパイルエラーが無くなりました。では"TwitterServiceTest.java"を実行しましょう。テストが成功するでしょう。
"TweetController"を修正して TwitterService#tweet() を呼び出したいと思います。まず最初に"TweetControllerTest" を以下のように修正します。

TweetControllerTest.java

package tutorial.controller.twitter;

import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

import org.slim3.datastore.Datastore;
import org.slim3.tester.ControllerTestCase;

import tutorial.model.Tweet;

public class TweetControllerTest extends ControllerTestCase {

    @Test
    public void run() throws Exception {
        tester.param("content", "Hello");
        tester.start("/twitter/tweet");
        TweetController controller = tester.getController();
        assertThat(controller, is(notNullValue()));
        assertThat(tester.isRedirect(), is(true));
        assertThat(tester.getDestinationPath(), is("/twitter/"));
        Tweet stored = Datastore.query(Tweet.class).asSingle();
        assertThat(stored, is(notNullValue()));
        assertThat(stored.getContent(), is("Hello"));
    }
}

ControllerTester(tester)#param() はフォームのパラメータをエミュレートします。
queryを実行してsingle modelを取得します。Datastore.query(Class modelClass).asSingle()を使用してください。
詳細はクエリの概要を参照してください。

"TweetControllerTest.java"を実行します。テストは失敗するでしょう。"TweetController.java"を修正してTwitterService#tweet()を呼び出していないからです。それでは"TweetController.java"を修正しましょう。

TweetController.java

package tutorial.controller.twitter;

import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;
import org.slim3.util.RequestMap;

import tutorial.service.TwitterService;

public class TweetController extends Controller {

    private TwitterService service = new TwitterService();
    
    @Override
    public Navigation run() throws Exception {
        service.tweet(new RequestMap(request));
        return redirect(basePath);
    }
}

Slim3はリクエストパラメータをリクエスト属性にコピーします。そしてRequestMapでリクエスト属性をMapにラップしています。
"TweetControllerTest.java"を実行します。テストが成功するでしょう。"TweetController.java"の準備ができました。それでは index.jspに戻って何か入力して投稿してみましょう。

つぶやいた後で http://localhost:8888/_ah/admin にアクセスします。Entity Kind list で Tweetを選択して"List Entities" ボタンをクリックすると、つぶやいた内容を確認することができます。




Comments