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」にあたるモデル部分の実装がテーマです。
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
参考になるエラーメッセージとグラフィカルなデバッガが、間違いの修正を容易にしてくれます。ふむふむ。期待。
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
最初の一回だけtrueを返して二回目以降はfalseを返す関数
職場で一時間考えてもできなかったのに自宅でやると5分でできるから不思議だ
var trueOnlyFirstTime = (function() { var b = true; return function() { if (b) { b = false; return true; } return false; }; })();