SiNBLOG

140文字に入らないことを、極稀に書くBlog

転職してました

大都会岡山 Advent Calendar 2013」の14日目!

ものすごく今更なのですが、転職して東京に旅立ちました。

今はトップゲートの愉快な仲間たちの一人です。

岡山にいる時から、GAEを趣味でごりごり触っていたのが、ついに仕事に!って感じです。

名刺にもGAEマイスターという格好良い称号が入り

俺が、俺たちが、GAEだ!!

と叫びながら、インスタンスが増えまくるとかやらないといけない雰囲気。

僕にとって、shin1ogawaさんとか、vvakameさんとかは、雲の上の人みたいな存在だったので、一緒に仕事したり、キリンを睡眠爆破したりしてるのが、とても不思議な感じ。

さて、東京に来て、もうそろそろ1年。

なんとなくの感想を適当に並べてみようかと思います。

Slim3 Source Code Readingは地味に有名だった。

GDGChugokuで毎週やっていたこの小さな会。

案外、東京でも知っている人が多く、"そのアイコンSlim3SourceCodeReadingやってた人?"と聞かれます。

世界が広いんだか、狭いんだか、よく分からなくなった瞬間でした。

今は同じノリで、golang勉強会をやってるので、行きたいけど、さすがに遠い!

色んな勉強会いっぱい

大都会岡山もかなりの数の勉強会があるけど、東京はやっぱり多い。

後、どの勉強会も結構人数がいる。

更に、実際にそれ使ったProduct仕事でやってますーって人も多い。

岡山だと新しい技術(もうすでに新しいとは呼べないものもあるけど)を仕事で使うのが難いことが多いけど、東京だと使っている人がたくさんいて賑やか。

そのぶん、濃いノウハウを持っている人も多くて、色んな話が聞けるのが楽しい。

ただひたすらに人が多い

僕の実家周辺だと、歩行者0とか普通だけど、東京だと大抵視界の中に誰か入っている。

ついでに、車もめっちゃ多い。

休みの日にバイクで出かけようにも、信号の数もかなりのもので、とてもめんどい。

後、駐車場がある施設も、かなり少ないので、つらみ。

電車社会。

終わり

岡山でのんきに過ごしていたかったけど、東京に行く流れがあったので、乗っかって1年経った。

なんだかんだで楽しく過ごしているから、良かったんだろう。

GDGChugoku には時折出現しているので、見かけたら、"なんでいるの?"って声をかけてあげてください。

AngularJSで一覧画面を作る

今日はAngularJSで一覧ページを作ってみました

動いているのは、以下のページ
AngularJS sample

ちょっと、データが適当過ぎて、なんのこっちゃ分からん感がすごいですが、DBから取得したデータを表示しています

登録しているデータは以下のエントリーで作ったFormで登録したデータ

AngularJSで連動して動くコンボボックスを作る Part 1 - SinDiary


まずはソース一覧を列挙して、ちょっとずつ解説をしていきます

index.html

<!DOCTYPE html>
<html lang="ja" ng-app="sample">
  <head>
    <meta charset="utf-8">
    <title>AngularJS sample</title>
    <meta name="description" content="">
    <meta name="author" content="">
    <!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
    <!--[if lt IE 9]>
      <script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]--><!-- Le styles -->
    <link rel="stylesheet" href="/lib/bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="/lib/bootstrap/css/bootstrap-responsive.min.css">
    <style type="text/css">
      body {
        padding-top: 30px;
        padding-left: 30px;
      }
    </style>
  </head>
  <body>
    <div>
  	  <h2>Hello AngularJS !!</h2>
      <div ng-view></div>
    </div>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script src="//code.angularjs.org/1.0.6/angular.min.js"></script>
    <script src="//code.angularjs.org/1.0.6/angular-resource.min.js"></script>
    <script src="/js/main.js"></script>
  </body>
</html>

main.js

(function() {
  var app = angular.module('sample', ['ngResource']).
    config(function($routeProvider) {
      $routeProvider.
        when('/', {controller:'ListController', templateUrl:'list.html'}).
        when('/entry', {controller:'EntryController', templateUrl:'entry.html'});
    });

  app.controller('ListController', ['$scope', '$resource', function($scope, $resource) {
    var Store = $resource("/store");
    $scope.stores = Store.query(function() {
      console.log("success store query");
    }, function(){
      console.log("error store query");
    });
  }]);

  app.controller('EntryController', ['$scope', '$location', '$resource', function($scope, $location, $resource) {
    $scope.categories = [{"id" : "1", "name" : "野菜"}];
    var List = $resource("/item/list");
    var Store = $resource("/store");

    $scope.changeCategory = function() {
      $scope.items = List.query({id : $scope.entryForm.categoryid}, function(){
        console.log("success list");
      }, function(){
        console.log("error list");
      });
    }

    $scope.submit = function($event) {
      console.log($scope.entryForm);
      Store.save($scope.entryForm, function(){
        console.log("success entry");
        $location.path('/');
      }, function(){
        console.log("error entry");
      });
    };
  }]);
})();

list.html

<div>
  <table class="table">
    <thead>
      <tr>
        <td>No</td>
        <td>CategoryId</td>
        <td>ItemId</td>
        <td>Name</td>
      </tr>
    </thead>
    <tbody ng-repeat="s in stores">
      <tr>
        <td>{{$index}}</td>
        <td>{{s.CategoryId}}</td>
        <td>{{s.ItemId}}</td>
        <td>{{s.Name}}</td>
      </tr>
    </tbody>
  </table>
  <a href="#/entry" class="btn btn-primary">Entry</a>
</div>

entry.html

<div>
  <form ng-submit="submit()">
    <select ng-model="entryForm.categoryId" ng-options="c.id as c.name for c in categories" ng-change="changeCategory()">
      <option value="">-- chose category --</option>
    </select>
    <br />
    <select ng-model="entryForm.itemId" ng-options="i.id as i.name for i in items">
      <option value="">-- chose item --</option>
    </select>
    <br />
    <input type="text" ng-model="entryForm.name"></input>
    <br />
    <button type="submit" class="btn btn-primary">Save</button>
    <a href="#/" class="btn">Cancel</a>
  </form>
</div>


これだけです
まずはファイルの構成ですが、以下の様な感じ

  • index.html - 最初に表示されるhtml
  • main.js - ControllerなどAngularJSを使っているJS
  • list.html - 一覧部分のDOMを持つtemplate
  • entry.html - EntryFormのDOMを持つtemplate

index.htmlには、ほとんど何も記述されておらず、主なDOMはtemplateにあります
これには以下のような理由があります

index.htmlにAngularJS用のDOMを書くと、AngularJS適用前にチラ見えする

今回だと、list.htmlにあるtableなんかが、index.htmlに書くと、最初にちらちら見えます
特に{{s.CategoryId}}とかが見えるので、ユーザにはなんじゃこりゃ?感が溢れ出ることでしょう
ということで、index.htmlには以下のように書いておきます

<div ng-view></div>

これで、templateの内容をこの中に表示してくれます
特にmoduleとかの名前も付いてないですが、1つのng-appの中で1つしか作れないのかな・・・?


後、重要なところはmain.jsの以下の部分でしょう

var app = angular.module('sample', ['ngResource']).
    config(function($routeProvider) {
      $routeProvider.
        when('/', {controller:'ListController', templateUrl:'list.html'}).
        when('/entry', {controller:'EntryController', templateUrl:'entry.html'});
    });

この部分でpathとController, templateを紐付けています
前のエントリーでcontrollerの範囲が分からないと言いましたが、上記だとそれっぽい範囲になっています
共通で使うmenuみたいなのがあると、また悩み始めると思いますが、少なくともページごとのcontrollerにはなってます

その他のソースは、だいたい前回のエントリーの内容と同じです
AngularJSで連動して動くコンボボックスを作る Part 1 - SinDiary
$routeProvider使う関係で細かい部分は変わっていますが


ソースはgithubにあるので、全体を見たい方は以下をご覧ください
Server側がgae/goではありますが・・・
GitHub - sinmetal/angularjs-sample


今は新規登録しかないですが、編集とか削除もぼちぼち作って行きたいと思います
後、もうちょっと中身のデータをまともなものにしたりとかw


疑問に思っていること
$routeProvider使った状態で、controllerをcontroller関数で生成する方法が分からない・・・
minify対策のこともあるし、直したいところ
AngularJSのDIの仕組み、minify対策は覚えておこう! - Qiita


2013-05-25 修正
コメントでminify対策用のcontrollerの書き方を教えてもらったので、修正しました!
ありがとうございます!

AngularJSで連動して動くコンボボックスを作る Part 1

以下のエントリーで作成したサンプルをGoogle+のAngularJS JPコミュニティにサンドバッグとして投下した結果

AngularJSで連動して動くコンボボックスを作る Part 0 - SinDiary


親切にふるぼっこにした上、pull requestまで送ってくれました!

G+
https://plus.google.com/u/0/114061805681880927213/posts/JpmZpC5CsNW

pull request
サンドバック part1 by teyosh · Pull Request #2 · sinmetal/angularjs-sample · GitHub


指摘してくださった内容は、以下の通り。

  • ng-appにmoduleを設定しましょう
  • controllerはcontroller関数で設定しましょう
  • categoriesの初期化にng-initはいらないかな
  • itemsとentryにはngResourceを使うと便利
  • factory使ったりdirectiveで隠蔽したりすればもっといいかも
  • form validationの機能もあるので使うといいかも

上記全て対応できたわけではないのですが、ある程度修正したのが以下のソース

index.html

<!DOCTYPE html>
<html lang="ja" ng-app="sample">
  <head>
    <meta charset="utf-8">
    <title>AngularJS sample</title>
    <meta name="description" content="">
    <meta name="author" content="">
    <!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
    <!--[if lt IE 9]>
      <script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]--><!-- Le styles -->
    <link rel="stylesheet" href="/lib/bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="/lib/bootstrap/css/bootstrap-responsive.min.css">
  </head>
  <body>
    <div ng-controller="MainController">
  	  <h2>Hello AngularJS !!</h2>
      <form ng-submit="submit()">
        <select ng-model="entryForm.categoryid" ng-options="c.id as c.name for c in categories" ng-change="changeCategory()">
          <option value="">-- chose category --</option>
        </select>
        <br />
        <select ng-model="entryForm.itemid" ng-options="i.id as i.name for i in items">
          <option value="">-- chose item --</option>
        </select>
        <br />
        <input type="text" ng-model="entryForm.name"></input>
        <br />
        <button type="submit" class="btn">submit</button>
      </form>
    </div>
    <script src="//code.angularjs.org/1.0.6/angular.min.js"></script>
    <script src="//code.angularjs.org/1.0.6/angular-resource.min.js"></script>
    <script src="/js/main.js"></script>
  </body>
</html>

main.js

(function() {
  var app = angular.module("sample", ["ngResource"]);
  app.controller("MainController", function($scope, $resource){
    $scope.categories = [{"id" : "1", "name" : "野菜"}];
    var List = $resource("/item/list");
    var Entry = $resource("/store/entry");
    $scope.changeCategory = function() {
      $scope.items = List.query({id : $scope.entryForm.categoryid}, function(){
        console.log("success list");
      }, function(){
        console.log("error list");
      });
    };
    $scope.submit = function($event) {
      Entry.save($scope.entryForm, function(){
        console.log("success entry");
      }, function(){
        console.log("error entry");
      });
    };
  });
})();

それぞれ、修正した箇所は以下の辺り

ng-appにmoduleを設定しましょう

<html lang="ja" ng-app="sample">
・・・
<script src="//code.angularjs.org/1.0.6/angular-resource.min.js"></script>
var app = angular.module("sample", ["ngResource"]);

ng-appにmoduleを設定することで、angularの中にControllerなどが作れるようになりました
名前空間も汚染しないし、良い感じです
ngResourceも使うようになったので、jsの読み込みも増えています


controllerはcontroller関数で設定しましょう

app.controller("MainController", function($scope, $resource){

ng-appにmoduleを設定したので、Controllerもangularの中に作ります。


categoriesの初期化にng-initはいらないかな

$scope.categories = [{"id" : "1", "name" : "野菜"}];

load時に最初のコンボボックスの初期化にng-initを使っていたのだけど、普通に一番上に書いておけば良いみたい


itemsとentryにはngResourceを使うと便利

var List = $resource("/item/list");
var Entry = $resource("/store/entry");
・・・
$scope.items = List.query({id : $scope.entryForm.categoryid}, function(){
・・・
Entry.save($scope.entryForm, function(){

Server側をREST APIの形にすると、もっと便利らしいのだけど、今はこんな感じに
$httpを使っていた時より、シンプルになって良いです


と今の段階だと、この程度ですが、まだまだ機能があるようで、勉強が必要です
アドバイスしてもらった内容で、まだ分かってないこともあるので、その辺りから攻めなきゃ


色々とアドバイスしてくださった上に、pull requestまで送っていただいたTessei Yoshidaさんありがとうございました!

AngularJSで連動して動くコンボボックスを作る Part 0

最近、AngularJSをちょこちょこと触り始めました。
しかし、元々jsをあまり知らないので、あんまり自信が無い・・・。


ということで、書いてみた内容をBlogに書くことで、ふるぼっこにされようメソッドを展開することに!

今回作ってみたのは、2つのコンボボックスが連動する画面です。

実際に動いているのは、以下
AngularJS sample


データが1つしか無いので分かりにくいですが、上のコンボボックスを選択すると、下のコンボボックスが変わるって感じです
本当にちょっと動くようにしてみただけって感じなので、ソースもちょっとしかないです。

index.html

<!DOCTYPE html>
<html lang="ja" ng-app>
  <head>
    <meta charset="utf-8">
    <title>AngularJS sample</title>
    <meta name="description" content="">
    <meta name="author" content="">
    <!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
    <!--[if lt IE 9]>
      <script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]--><!-- Le styles -->
    <link rel="stylesheet" href="/lib/bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="/lib/bootstrap/css/bootstrap-responsive.min.css">
  </head>
  <body>
    <div ng-controller="MainController" ng-init="init()">
  	  <h2>Hello AngularJS !!</h2>
      <form ng-submit="submit()">
        <select ng-model="entryForm.categoryid" ng-options="c.id as c.name for c in categories" ng-change="changeCategory()">
          <option value="">-- chose category --</option>
        </select>
        <br />
        <select ng-model="entryForm.itemid" ng-options="i.id as i.name for i in items">
          <option value="">-- chose item --</option>
        </select>
        <br />
        <input type="text" ng-model="entryForm.name"></input>
        <br />
        <button type="submit" class="btn">submit</button>
      </form>
    </div>
    <script src="//code.angularjs.org/1.0.6/angular.min.js"></script>
    <script src="/js/main.js"></script>
  </body>
</html>

main.js

function MainController($scope, $http) {
	$scope.init = function() {
		$scope.categories = [{"id" : "1", "name" : "野菜"}];
	};

	$scope.changeCategory = function() {
		$http({
			method: 'GET', 
			url: '/item/list',
			param: $scope.categoryid
		}).
  		success(function(data, status, headers, config) {
  			$scope.items = data;
  		}).
		error(function(data, status, headers, config) {
			console.log('error');
		});
	};

	$scope.submit = function() {
		$http({
			method: 'POST', 
			url: '/store/entry',
			param: $scope.entryForm
		}).
  		success(function(data, status, headers, config) {
  			console.log('success');
  		}).
		error(function(data, status, headers, config) {
			console.log('error');
		});
	};
}

AngularJSの使っている項目は以下の通り

  • ng-controller
  • ng-init
  • ng-model
  • ng-options
  • ng-change
  • ng-submit


それぞれ何に使っているかと、迷っているところを書いていこう

ng-controller
ng-controllerを使わないとAngularJSが始まらない気がするので、formをごそっと囲っている
ただ、気になるのはng-controllerで囲う範囲だ
今はよく分からず、とても大きな範囲で囲っている
それこそ、ページを全部1つのcontrollerで囲う勢いだ
でも、controllerはネストしたりできるようなので、本当はもっと小さく囲うのが正しいのかもしれない
今回だと、コンボボックスが$httpでデータを取ってきたりしているので、select付近に小さなcontrollerとか作ったりするのかもしれない
その場合、controller間のデータのやり取りの仕方がよく分からなかったので、今は大きく囲っちゃっている


ng-init
ng-initは初期処理を呼び出すのに使っている。
これもあんまり良くわかってないけど、今はページ単位で1度だけng-initを使っている
もしかしたら、ng-initがあるDOMが読み込まれた時に1度だけ動くとかなら、必要な箇所で都度書いても良いのかも


ng-model
ng-modelはformの中身を管理するのに使っている
この画面だとformをpostするしかやることがないので、formの中身を持っている
controllerを複数使う場合は、それぞれng-modelがあるのだろうから、また違ってくるのかもしれない


ng-options
ng-optionはoptionタグを動的に出力するのに使っている
Referenceにはいくつかのパターンが記述されていたが、group byってのが何なのかよく分かっていない


ng-change
ng-changeはコンボボックスが変更された時のイベントハンドラとして登録している
これは、こんな感じの使い方で良いんじゃないかと思っている


ng-submit
ng-submitはformがsumitされた時のイベントハンドラとして使っている
$httpのparamにng-modelを渡しているが、これはこんな感じで良いのかな・・・?


と色々と疑問は尽きない
日本語の情報もまだまだ少ないし、これからも試してみた内容をBlogにちょこちょこ書いていきたい

もっといい方法があるよ!って場合は、コメントでもtwitterでもG+でも何でも良いので
教えてもらえると、とっても嬉しい!


上記のソースはgithubに置いている
何故かServer側を適当にgae/gで作ってしまったので、そこはスルーするか、gae/gの方にもツッコミを貰えると嬉しいw
https://github.com/sinmetal/angularjs-sample


2013-05-18追記
色々とアドバイスを貰ったので、更新しました!
AngularJSで連動して動くコンボボックスを作る Part 1 - SinDiary

bigqueryのjoin eachとgroup each by

join eachを使う時にサブクエリの中でgroup byが使えないと書きました

2013-04-28 - SinDiary

しかし、これの解決策を教えてもらいました


それがgroup each by

公式ドキュメント見ても、いまいち使いどころが分かっていなかった、これ

join eachとセットで使うのが基本なのかな・・・?

前回のsqlをgroup each byで書くとこんな感じ


SELECT p1.pokemonName FROM [pokemonms.Pokemon] p1
JOIN EACH
(SELECT worldGuideNo, pokemonName
FROM [pokemonms.Pokemon]
GROUP EACH BY worldGuideNo,pokemonName
) p2 ON p1.worldGuideNo = p2.worldGuideNo

group byがgroup each byってなっただけですね!

ということで、以下のようです


join eachで右側のテーブルにサブクエリを使い かつ サブクエリにgroup byがある場合、group each byを使う


これで、small joinで収まりきらなくなったものをlarge joinにする時も、ちょっと修正するだけでいけますね

bigqueryのjoin eachはサブクエリのgroup byに対応していない?

先日、bigqueryに待望のlarge joinが入りました!
Google Developers Blog: BigQuery gets big new features to make data analysis even easier

というわけで、試していたのですが、ちょっと気になるところが・・・
joinする右のテーブルがサブクエリで、group byを使っていた時にエラーになる?


SELECT p1.pokemonName FROM [pokemonms.Pokemon] p1
JOIN EACH
(SELECT worldGuideNo, pokemonName
FROM [pokemonms.Pokemon]
GROUP BY worldGuideNo,pokemonName
) p2 ON p1.worldGuideNo = p2.worldGuideNo


Error: SHUFFLE BY may only be applied to parallelizable queries, but query is not parallelizable: (SELECT [worldGuideNo], [pokemonName] FROM [gen_726962906418_pokemonms.Pokemo...

こんな感じでエラーになっちゃうのです
テーブルのデータ量は数件で、結果も大したことはありません
また、JOIN EACHをJOINに変えれば、動きます


普通のJOINの方がコストが低いので、データが少ないうちはJOINでやっておいて、
small joinの制限を超えたら、JOIN EACHに書き換えれば良いと思っていたのですが、
これだと、SQLの修正が必要になってしまいます


でも、googleの中の人に連絡したら、もしかしたらバグかもーと仰っていたので、
JOIN EACHでサブクエリにgroup by使っていても動くようになるのかも!


2014/05/06追記 エラー回避方法は以下の記事
2013-05-06 - SinDiary

gae/gを動かす

今日はAppengine上で動く第3の言語であるGoについてです

僕がgae/goしたい理由は以下の3つです

spin-upがきちがいじみて早い

実行速度そのものも早い (並行処理を言語レベルでサポートしてくれている)

Javaより書くのが楽しそう

以上のような理由から、gae/g楽しそうだなーと思ってます

experimental早く外れないかなー


gae/gを動かすには、goをインストール必要があります

インストール方法は公式を見ても良いし

日本語なら、Go言語のインストール - golang.jpを見るのも良いと思います

また、ymotongpoo / goenv — Bitbucketを使うのも良いかも

※gae/gのSDKの中にgoがあるから、go installしなくても良いんじゃないかと教えてもらいました

さて、goがインストールできたら、gae/gをSDKをdownloadします

Download and Install the SDK for App Engine  |  App Engine Documentation  |  Google Cloud

僕の環境はMacなので、go_appengine_sdk_darwin_amd64-1.7.7.zipをdownloadしました

こいつを適当な場所に解凍します

僕の環境では、以下に解凍しました


~/bin/google_appengine


それでは、適当な何かを作っていきます

http://www.amazon.co.jp/gp/product/4798031801?ie=UTF8&camp=1207&creative=8411&creativeASIN=4798031801&linkCode=shr&tag=sinmetal-22&qid=1365943307&sr=8-1&keywords=go+appengine

今回は横山さんの本に出てくる簡単なguest bookを作って見ることにしました

しかし、それには試練があります

本が出た後に、golangとgaeのversionが上がっていて、サンプルがそのままでは動かない・・・!

ということで、version上がって変わった辺りを適当にクリアしながら、進んでいきます


まず、作業用のディレクトリを作ります


~/bin/google_appengine/apps/gae-g-book

このディレクトリの中にファイルを作っていきます


最初にAppengineの設定ファイルであるapp.yamlを書きます

app.yaml

application: gae-g-book
version: 2013-01-27
runtime: go
api_version: go1

handlers:
- url: /style\.css
  static_files: html/style.css
  upload: html/style.css

- url: /.*
  script: _go_app

gae/pをしている人には、お馴染みにファイルなのかも

僕はgae/jしかしたことがないので、よく分からないけど

gae/jでは、appengine-web.xmlに相当するファイルです

このままでは、よく分からないので、解説を入れてみました。

application: gae-g-book // application名 
version: 2013-01-27 // appengine にデプロイする時に使うversion
runtime: go // go言語を使うので、go
api_version: go1 // go version 1を使うので、go1

handlers: // cssファイルをstatic fileとして登録している
- url: /style\.css
  static_files: html/style.css
  upload: html/style.css

- url: /.* // 全部のURLをgoで処理する
  script: _go_app

こんな感じです


そして、実際にリクエストが来た時の処理が以下

main.go

package process

import (
	"appengine"
	"appengine/datastore"
	"fmt"
	"log"
	"net/http"
	"text/template"
	"time"
)

// Datastoreにぶっこむために使う
type Guest struct {
	Name string
	Date time.Time
}

// 画面表示用に使う
type Guest_View struct {
	Name string
	Date string
}

// リクエストが来た時のルーティング
func init() {
	http.HandleFunc("/", handler)
	http.HandleFunc("/write", write)
	http.HandleFunc("/list", list)
}

const inputForm = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>名前の登録</title>
</head>
<body>
<form method="POST" action="write">
	<label>お名前<input type="text" name="name" /></label>
	<input type="submit">
</form>
</body>
</html>
`

const guestTemplateHTML = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登録者リスト</title>
</head>
<body>
	<table border="1">
		<tr>
			<th>名前</th>
			<th>登録日時</th>
		</tr>
		{{range .}}
			<tr>
				{{if .Name}}
					<td>{{.Name|html}}</td>
				{{else}}
					<td>名無し</td>
				{{end}}
				{{if .Date}}
					<td>{{.Date|html}}</td>
				{{else}}
					<td> - </td>
				{{end}}
			</tr>
		{{end}}
	</table>
</body>
</html>
`

var guestTemplate = template.Must(template.New("guest").Parse(guestTemplateHTML))

// htmlをそのままResponseにぶっこむ
func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "%s", inputForm)
}

// submit された値を、Datastoreに入れる
func write(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		w.WriteHeader(http.StatusNotFound)
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		fmt.Fprintf(w, "Not Found")
		return
	}

	c := appengine.NewContext(r)

	// Datastoreへの書き込み
	var g Guest
	g.Name = r.FormValue("name")
	g.Date = time.Now()
	if _, err := datastore.Put(c, datastore.NewIncompleteKey(c, "Guest", nil), &g); err != nil {
		http.Error(w, "Internal Server Error : "+err.Error(), http.StatusInternalServerError)
		return
	}

	http.Redirect(w, r, "/", http.StatusFound)
}

// Datastoreに入っている値の一覧を表示
func list(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	q := datastore.NewQuery("Guest").Order("Date")
	count, err := q.Count(c)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	guests := make([]Guest, 0, count)
	if _, err := q.GetAll(c, &guests); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	log.Printf("guests len=%d", len(guests))

	guest_views := make([]Guest_View, count)
	for pos, guest := range guests {
		guest_views[pos].Name = fmt.Sprintf("%s", guest.Name)
		localTime := guest.Date.Format("2006/01/02 15:04:05")
		guest_views[pos].Date = localTime
	}

	if err := guestTemplate.Execute(w, guest_views); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

簡単ですが、こんな感じです


こいつを、dev serverで動かしてみます


cd ~/bin/google_appengine/apps
dev_appserver.py gae-g-book

これで、dev serverが動きます


INFO 2013-04-14 13:49:51,111 dispatcher.py:150] Starting server "default" running at: http://localhost:8080
INFO 2013-04-14 13:49:51,113 admin_server.py:117] Starting admin server at: http://localhost:8000

上記のようなメッセージが表示されて、dev server と admin serverのURLが分かります

因みに僕が2月ぐらいに試した時は、admin serverはhttp://localhost:8080/_ah/adminとかだった気がしたけど、変わった?


以上で、今回おしまいです

細かいところの説明は、飛ばしちゃったりしているので、分からないことがあったらコメントかtwitterで聞いちゃってください

僕もまだgae/gは触り始めたばかりですが、面白そうなので、これからも触っていきたいと思います

UnitTestはどうすれば良いんだ?とか、mavenみたいなのあるのかな?とか、まだまだ疑問はありますが、少しずつ・・・


今回のソースはGithubに上げてあります

GitHub - sinmetal/gae-g-book