RingoJSのチュートリアルをやってみる - データストアの設定


久しぶりにRingoJSを触ってみます。チュートリアルConfiguring a Storeっていうところ。いよいよデータストアでしょうか。楽しみです。

RingoJSでは「model.js」というファイルにモデルの定義を書いていきます。データベース定義みたいなものですきっと。ただ、データだけではなく、モデルの振る舞いも併せて書くことができます。むしろこっちが本題な気がします。

規模の大きなプロジェクトでは「model」というディレクトリを作成し、その下に「モデル名.js」というファイルを作成しくこともできます。チュートリアルは単一の「model.js」で進めていきます。

model.js

チュートリアルには以下のような記述がありました。

// model.js
var filestore = require('ringo/storage/filestore');
var storePath = './db';
var store = filestore.Store(storePath);

exports.Post = store.defineEntity('Post');
exports.Comment = store.defineEntity('Comment');

が、このままだと動きませんでした。しばらく触ってないうちに使い方が変ったのかな。filestoreのStore関数は値を返さなくなってフォルダのパスを指定するだけの関数になったようです。defineEntity関数もモジュールレベルの関数になったみたい。

// model.js
var {Store} = require('ringo/storage/filestore');
Store('./db');

exports.Post = defineEntity('Post');
exports.Comment = defineEntity('Comment');

プログラムでエンティティの定義ができるというのは良いですね。上の例はチュートリアルなので分かりやすく書かれていましたが、以下のようにリファクタリングすることができます。

['Post', 'Comment'].forEach(function(model) {
  exports[model] = defineEntity(model);
});

ファイルストレージでエンティティを保存してみる

まずはbin/ringoの対話コンソールで動かしてみます。

>> var {Post, Comment} = require('model')
>> var post = new Post();
>> post.title = 'kyou ha yuki ga futta!';
kyou ha yuki ga futta!
>> post.body = 'untara kantara ~~~';
untara kantara ~~~
>> post.save();

Postエンティティを作成してtitleとbodyを設定してsave関数で保存してみました。
するとdbディレクトリの下にエンティティの名前でディレクトリが作られて、その下に連番のIDかな?とにかく「1」っていうファイルができていました。中身を見るとJSON形式のデータが格納されているようです。

$ find db
db
db/Post
db/Post/1
$ cat db/Post/1
{"title":"kyou ha yuki ga futta!","body":"untara kantara ~~~"}

眠くなってきた。今日はここまで。

感想

Google App Engineで使えるからって聞いてチュートリアルを始めたのに、ファイルストレージへのアクセスがチュートリアルに出てきたのはビックリした。あんまりGAEメインっていうものでもないのかな。リレーショナルデータベース用のデータストアもあるみたいだし。
早くGoogle App Engineのデータストアにデータを書いたり読んだりしたい。

RingoJSのチュートリアルをやってみる 3日め

久しぶりにRingoJSのチュートリアルの続きをやってみます。今日はMVCの「M」にあたるモデル部分の実装がテーマです。

Tutorial - RingoJS

Configuring a Store

モデルの実装は「model.js」という名前のファイルに書くのが慣習になっているようです。

// model.js
var filestore = require('ringo/storage/filestore');
var storePath = './db';
var store = new filestore.Store(storePath);

1行目で「filestore」ライブラリを読み込んで、3行目でfileStore.Storeオブジェクトを生成しています。newとか使うんですね。JavaScriptなのにオブジェクト指向プログラミングっぽいです。
ちなみに、「ringo/storage」の下には以下のファイルが並んでいました。ファイルストアの他にgoogleストアとメモリストアがあるみたいです。googleストアはGoogle App Engineのデータストアなんだろうけど、普通のRDBストアはないのかな?よくわかりません。

filestore.js
googlestore.js
memstore.js
querysupport.js
storeutils.js
エンティティを定義する

Storeオブジェクトは「defineEntity」というメソッドを持っています。ソースコードの中でエンティティを定義するわけですね。

// model.js
var filestore = require('ringo/storage/filestore');
var storePath = './db';
var store = new filestore.Store(storePath);

exports.Post = store.defineEntity('Post');
exports.Comment = store.defineEntity('Comment');

エンティティを定義すると同時にエクスポートしています。「Post」と「Comment」をエクスポートしているので、この2つは他のモジュールから利用できるようになっています。

エンティティを使ってみる

せっかくエンティティを定義したのでシェルから呼び出して使ってみましょう。

$ ringo
>> var model = require('model');
>> var post = new model.Post();
>> post.title = 'たいふうが じょうりくしたよ';
たいふうが じょうりくしたよ
>> post.body = '今日は台風が来て雨と風がひどかったのでおうちでおとなしくしていました。';
今日は台風が来て雨と風がひどかったのでおうちでおとなしくしていました。
>> post.save();

model.jsからrequireしたPostのオブジェクトを生成して、プロパティを適当にセットして、save()メソッドを呼ぶと保存できました。
ファイルストアなので、ファイルに書き出されます。

$ cat db/Post/1
{"title":"たいふうが じょうりくしたよ","body":"今日は台風が来て雨と風がひどかったのでお風がひどかったのでおうちでおとなしくしていました。"}

「1」というファイル名で中身はjson形式で保存されていました。なるほど。


get()メソッドで、IDを指定してエンティティをロードすることができます。filestoreの場合、ファイル名がIDになるようです。

>> var model = require('model');
>> var post = model.Post.get(1)
>> post.title
たいふうが じょうりくしたよ
>> post.body
今日は台風が来て雨と風がひどかったのでお風がひどかったのでおうちでおとなしくしていました。


all()メソッドで、全てのエンティティをロードすることができます。

>> var posts = model.Post.all();
>> posts.length
1
>> posts[0].title
たいふうが じょうりくしたよ
>> posts[0].body
今日は台風が来て雨と風がひどかったのでお風がひどかったのでおうちでおとなしくしていました。

RingoJSのチュートリアルをやってみる 2日め

今日もRingoJSのチュートリアルをやってみます。あんまり時間がとれないので少しずつやってます。

JavaScript Modules

自分で書いたJavaScriptファイルを、他のプロジェクトから使うときのやり方が書いていました。

モジュールを輸出する - exports

まず、使われる側は、公開したい関数を exports というグローバル変数(?)のプロパティに代入します。
すると、他のプログラムは、エクスポートされた関数を使用できるようになります。逆に exports にセットしていない関数や変数は他から見えないので、グローバル名前空間が汚れるのを気にする必要がありません。素晴らしいですね。

// mymodule.js

// 外部に公開する関数
exports.sayHello = function() {
  print('Hello World!');
};

// 公開しない関数
function sayButuButu() {
  print('Ore nanka do-se...');
}
モジュールを要求する - require

モジュールを使う側は require() 関数を使います。
引数にはモジュールのファイル名から「.js」を除いた文字列を渡します。require()関数は、読み込んだファイルの中のexports変数を戻り値として返します。exports変数にはモジュールが公開した関数だけが入っているので、呼び出し側は公開されている関数だけを使うことができるわけです。

var mymodule = require('mymodule');

mymodule.sayHello(); // OK
mymodule.sayButuButu(); // エラー!
モジュールの一部だけをrequire

JavaScriptの新しい文法を使って、モジュールが公開している関数の一部だけを、直接取り出して使うことができます。この書き方は初めて見たのでびっくりしました。

var {sayHello} = require('mymodule');
sayHello();

JavaScriptの文法も変わっていくのですねえ。
↓にいろいろ載っていました。英語が得意な人は読んでみて私に教えてください><。
New in JavaScript 1.7 - MDC

モジュールを自身に含める - include

モジュールを使う手段に include() 関数というのが用意されています。
require()と同じようにモジュールのファイル名(- .js)を渡して実行するのですが、exports変数にセットされた関数たちを、自分とこの名前空間に展開します。ローカル変数とか、名前がかぶっていたりすると、知らないうちに上書きされたりして危険な感じがします。Ringoシェルで対話的に実行するときだけ使うのがよいみたいです。

include('mymodule');

sayHello(); // OK
sayButuButu(); // エラー!
モジュールのファイルを探すパス

require.paths 配列にパス文字列の配列が入っています。これにパスを追加してあげれば、そこにあるファイルをモジュールとして探すようになります。

>> require.paths
/Users/makoto/work/lang/javascript/ringojs/ringojs/apps/demoblog/,/Users/makoto/work/lang/javascript/ringojs/ringojs/modules/,/Users/makoto/work/lang/javascript/ringojs/ringojs/packages/namespace-skeleton/lib/
>> require.paths.unshift('/path/to/modules');
4

余談ですが、include.pathsには何も入っていませんでした。

思ったこと

モジュールの仕組みを使うと、クロージャっぽいものがすっきりと書けるようになるんじゃないかと思いました。

var trueOnlyFirstTime = (function() {
  var b = true;
  return function() {
    if (b) {
      b = false;
      return true;
    }
    return false;
  };
})();

最初の一回だけtrueを返して二回目以降はfalseを返す関数 - syttruの日記

上のような、ファンクションファンクションしていてよくわからなかったコードが、こんな感じで書けます。

var b = true;
exports.trueOnlyFirstTime = function() {
  if(b) {
    b = false;
    return true;
  }
  return false;
};

ちょっと見通しがよくなったんじゃないでしょうか。どうでしょう。


今日はここまで。

RingoJSのチュートリアルをやってみる


チュートリアルを見ながら、今日もRingoJSを触ってみます。

Webapp Scaffolding

Webアプリーションのひな型を作成するコマンドがあります。

$ bin/ringo-admin create apps/demoblog

上のコマンドを叩くと、「apps」ディレクトリの下に「demoblog」というディレクトリが作成されました。この中にWebアプリケーションのひな型が入っているのでしょう。ピヨピヨ。

apps/demoblogフォルダには3つのjavascriptファイルが入っていました。

  • main.js
  • config.js
  • actions.js

それから3つのフォルダがありました。

  • skins/
  • public/
  • config/

ringoコマンドにmain.jsを渡して実行するとサーバーが起動します。

$ cd apps/demoblog
$ ../../bin/ringo main.js
0    [main] INFO  ringo.webapp.daemon  - init
1008 [main] INFO  ringo.webapp.daemon  - start
1010 [main] INFO  org.eclipse.jetty.util.log  - jetty-7.1.6.v20100715
1143 [main] INFO  org.eclipse.jetty.util.log  - Started SelectChannelConnector@0.0.0.0:8080

昨日と同じようなログが出力されてサーバーが起動しました。
http://localhost:8080 で参照できます。

main.js

main.jsはwebアプリケーションを起動するスクリプトみたいです。
正直、何が書いてあるのかよくわかりませんが、このファイルを編集するのはまだ先でしょうからあまり考えないでおきます。

#!/usr/bin/env ringo

// main script to start application

if (require.main == module) {
    require("ringo/webapp").main(module.directory);
}

config.js

ミドルウェアとかデータベースとかアプリケーションそのものとか、その他いろいろな設定を書くファイルみたいです。
結構長いので少しずつ見ていきます。

// URL routing. Using require() here to statically import modules will
// improve performance, but may cause hard to debug cyclic module dependencies
// in case any app module requires this module.
exports.urls = [
    ['/', './actions'],
];

URLルーティングの設定みたいですね。親切にコメントが書かれていますが、、、読めません><。URL「/」が「actions.js」に対応するってことだと思います。

// Middleware stack as an array of middleware factories. These will be
// wrapped around the app with the first item in the array becoming the
// outermost layer in the resulting app.
exports.middleware = [
    require('ringo/middleware/gzip').middleware,
    require('ringo/middleware/etag').middleware,
    require('ringo/middleware/static').middleware(module.resolve('public')),
    // require('ringo/middleware/responselog').middleware,
    require('ringo/middleware/error').middleware('skins/error.html'),
    require('ringo/middleware/notfound').middleware('skins/notfound.html'),
];

ミドルウェアのスタック」と書いてあります。謎です。

// The JSGI application. This is a function that takes a request object
// as argument and returns a response object.
exports.app = require('ringo/webapp').handleRequest;

「JSGIアプリケーション」と書いてあります。謎です。

// Standard skin macros and filters
exports.macros = [
    require('ringo/skin/macros'),
    require('ringo/skin/filters'),
];

謎です。

// Default character encoding and MIME type for this app
exports.charset = 'UTF-8';
exports.contentType = 'text/html';

やっと謎じゃないのが出てきました。レスポンスのデフォルト文字コードとコンテントタイプですね。見覚えのある単語が出てきてやっと一安心です。データベースの設定とかするところがなかった気がしますが、今は気にしないでおきます。

actions.js

var {Response} = require('ringo/webapp/response');

exports.index = function (req) {
    return Response.skin(module.resolve('skins/index.html'), {
        title: "It's working!"
    });
};

「index」に関数が代入されています。関数はResponse.skin()関数に、ファイル(skins/index.html)と、置き換え文字列の連想配列{title: "It's working!"}を渡したものを返すみたいです。

ファイル(skins/index.html)も見てみましょう。

skins/index.html

<% extends ./base.html %>

<% subskin content %>
<div id="header"><h1><% title %></h1></div>
<div id="body">
<p>You just created a new Ringo application. Here are some possible next steps:</p>
<ul>
    <li>Tweak the URL routing or middleware stack in <code>config.js</code>.</li>
    <li>Edit and add actions in <code>actions.js</code>.</li>
    <li>Adapt the templates in the <code>skins</code> directory to your liking.</li>
    <li>Install one of the available <a href="http://ringojs.org/wiki/Packages/">database
        packages</a> to store application data.</li>
    <li>Visit our <a href="http://ringojs.org/wiki/Tutorial/">tutorial</a> or
        <a href="http://ringojs.org/wiki/Documentation/">documentation</a> to learn more about Ringo.</li>
</ul>
<p>Thank you for using Ringo!</p>
</div>

h1タグに囲まれて、置き換え文字列っぽい「title」の文字が見えますね。ここが置き換わるのでしょう。

「base.html」を継承しているようなので、これも見てみます。

skins/base.html

<!DOCTYPE html>
<html>
<head>
<title><% title %></title>
<link rel="stylesheet" href="/stylesheets/page.css" />
<% render head %>
</head>
<body>
<% render content %>
</body>
</html>

<% subskin content %>
Overwrite this subskin to add content.

テンプレート(index.html)が他のテンプレート(base.html)を継承して、subskin contentを上書きしている。っていう感じみたいです。

編集してみる

actions.jsを編集して、アクションを追加してみます。

var {Response} = require('ringo/webapp/response');

exports.index = function (req) {
    return Response.skin(module.resolve('skins/index.html'), {
        title: "It's working!"
    });
};

exports.test = function(req) {
    return Response.skin(module.resolve('skins/index.html'), {
        title: '動いたよ!"
    });
};

exports.testに関数をセットしてみました。そして置き換え文字列「title」を日本語にしてやりました。ファイルを保存して http://localhost:8080/test を開いてみると

日本語になりました。やったね!

サーバーを再起動しなくても変更が反映されました。これがInstant reloadingってやつなのでしょう。SAStrutsではhot deployとかhot reloadingとか呼ばれていました。

まとめ

無計画に試しながら書きながらしていたらまとまりがなくなってきました。まとめます。

RingoJSにはwebアプリのひな型を作るコマンドが付属している

$ ./bin/ringo-admin create [フォルダ]

コマンドを実行すると、以下のファイルとフォルダが作られる

  • main.js Webアプリを起動するスクリプト。ブートストラップ。中身はあまり気にしない。
  • config.js 各種設定を書くファイル。URLルーティングもここ。設定ファイルだけどJavaScriptソースコード
  • actiongs.js config.jsの中でURLに関連つけられている。レスポンスを返す関数を記述していく。MVCのView。

以下のフォルダも作られる

  • skins/ テンプレートファイル。actions.jsから読み込まれる?
  • public/ CSSとか、画像とか、(ブラウザで動作する)JavaScriptとか、静的コンテンツを置くフォルダ。
  • config/ webサーバの設定ファイル。「Don't worry」と書いていたので気にしないことにします。

RingoJS 触ってみた

RingoJSというプロジェクトが個人的に話題になっています。
JavaScriptでWebアプリケーションのサーバー側のプログラムを書くことができるみたいです。

特徴

RingoJSのトップページに書いてたことを泣きながら読んでいますが、上手く読めていません。英語爆発しろ。

Instant reloading

ソースコードを変更したら、ブラウザをリロードするだけで反映されるみたいです。サーバーの再起動をしなくてもよいってことなのかな。

Full web support

Webアプリケーションを構築する上で必要になるほとんどのものが付属しています。フルスタックってやつですね。

Easy debugging

参考になるエラーメッセージとグラフィカルなデバッガが、間違いの修正を容易にしてくれます。ふむふむ。期待。

Pure Java

RingoはJavaで書かれているので、たくさんのJavaライブラリとかを使えるみたいです。

It's fast

Ringoは世界最速のJavaScriptランタイムってわけじゃないけど、平均的なWebアプリケーションを引き裂くきます。(よくわかりません><)

インストール

Getting Startedのページからzipをダウンロードして解凍すればOKです。
gitやantの用意がある人はgithubから持ってきてantでビルドしてもいいみたいです。

git clone git://github.com/ringo/ringojs.git
cd ringojs
ant jar

Ringoシェル

RingoJSには対話式のシェルが付いています。RingoJSをインストールしたディレクトリで以下のコマンドを実行すると、シェルが起動します。

./bin/ringo

シェルが起動したので何か書いてみましょう

alert('Hello World!');
ReferenceError: "alert" is not defined. (<stdin>#1)
	at <stdin>:1

エラーになってしまいました。JavaScriptとはいえWebブラウザで動いているわけではないのでalertは使えないんですね。「window」とか「console」もundefinedでした。

色々試した結果、出力には「print」関数が使えることがわかりました。

['Hello', 'World', '!'].forEach(function(elem){
  print(elem);
});
Hello
World
!
[1,2,3,4,5,6,7,8,9,10].map(function(n) {
  return n * 2;
});
2,4,6,8,10,12,14,16,18,20

Array.map()やArray.forEach()も使えるみたい。素敵だ。

Webアプリケーションを起動してみる

シェルで遊んでいたら30分くらい経過してしまいました。今日の目玉であるWebアプリケーションを起動してみましょう。
Ringoをインストールしたフォルダの「apps」フォルダにデモアプリが入っています。

以下のコマンドを実行するとWebアプリケーションが起動します。

$ ./bin/ringo apps/demo/main.js
0    [main] INFO  ringo.webapp.daemon  - init
1034 [main] INFO  ringo.webapp.daemon  - start
1035 [main] INFO  org.eclipse.jetty.util.log  - jetty-7.1.6.v20100715
1151 [main] INFO  org.eclipse.jetty.util.log  - Started SelectChannelConnector@0.0.0.0:8080

http://localhost:8080/を開くと何かそれっぽいものが表示されました。


ちょっと気になったのでmain.jsの中を見てみました。

// main script to start application
if (require.main == module) {
    require('ringo/webapp').main(module.directory);
}

ほんげー。
なんだかよくわかりません。

Excelにテンプレートを書いて動的に帳票を生成するSeasarのプロジェクト Fisshplate

Fisshplate - Fisshplate Home

↑の画像をみた瞬間「これだよー!」と叫んでしまいました。POIを使ってExcelを作るたびに「ExcelにEL式を書けたらいいのになあー」みたいなことを思ってたのです。こんなのがあったんですね。知らなかった。未来のためにメモしておきます。

最初の一回だけtrueを返して二回目以降はfalseを返す関数

職場で一時間考えてもできなかったのに自宅でやると5分でできるから不思議だ

var trueOnlyFirstTime = (function() {
  var b = true;
  return function() {
    if (b) {
      b = false;
      return true;
    }
    return false;
  };
})();