[JavaScript] Getter/Setterをオブジェクト初期化子で定義する方法

JS

koaのソースコード を読んで知ったのでメモ。

一般的な定義方法

JavaScriptではオブジェクトにSetter/Getterを定義できます。
Setter/Getterとは、プロパティの設定時・参照時に呼び出されるメソッドのことです。

一般的には以下のように定義します:

var o = function() {};
o.prototype.__defineGetter__("b", function() { return this.a + 1; });
o.prototype.__defineSetter__("c", function(x) { this.a = x / 2; });

var i = new o();
i.c = 10;
console.log(i.b);

オブジェクト初期化子を使った定義

このSetter/Getterは、オブジェクト初期化子を使っても定義できます。

var o = {
  a: 7,
  get b() { return this.a + 1; },
  set c(x) { this.a = x / 2; }
};

var i = Object.create(o);

このように、get, setプレフィックスをつけて関数を定義します。

ブラウザ側で使う際の注意

MDNのドキュメント によると、ブラウザ実装状況は以下の通りです:

機能 Firefox (Gecko) Chrome Internet Explorer Opera Safari
基本サポート 2.0 (1.8.1) 1 9 9.5 3

サポートされていない場合 (特にIE6-8において) 、スクリプトはシンタックスエラーを引き起こします。

参考資料

[nodejs] DynamoDB ODMのvogelsをJSON形式に対応させた

DynamoDB

DynamoDBがJSONデータをサポートした

今月(2014年10月10日)のことですが、Amazon DynamoDBがいろいろアップデートしました:

その中でもJSONデータサポートは注目のアップデートです。
これは具体的にどういうことか説明します。
DynamoDBはスキーマレスのNoSQLです。
これまで、カラム(属性)には単一データまたはセット(重複を許さない配列)が格納できました。
しかしながら、入れ子構造は格納できませんでした。
JSONデータのサポートとは、この入れ子構造が取り扱えるようになったということです。
つまり、MongoDBのように、JSON形式のデータをそのままデータベースに格納できるようになったのです。

既存ODMライブラリに手を加えて対応させた

node.js向けのODMライブラリにはいくつかあります。
vogels はその中でもいい感じのライブラリです。
しかし残念ながら、あまり精力的にメンテナンスされてるとは言いがたく、今回のアップデートに対してもまだ対応していない様子です。
そこで、自分で対応させてみることにしました。
フォークしたものがこちらにあります:

いちおうプルリクしてありますが、マージされるのを待てません笑

vogelsの使い方

まずは基本的な使い方をドキュメントから引用してざっくり説明します。
次に、入れ子構造のデータを扱うための方法を説明します。

設定

AWS SDKのキーをファイルから設定します。

var vogels = require('vogels');
vogels.AWS.config.loadFromPath('credentials.json');

もちろん直接値を渡して設定もできます。

var vogels = require('vogels');
vogels.AWS.config.update({accessKeyId: 'AKID', secretAccessKey: 'SECRET'});

基本

データモデルの定義方法は以下のとおりです:

var Account = vogels.define('Account', function (schema) {
  schema.String('email', {hashKey: true});
  schema.String('name').required(); // name attribute is required
  schema.Number('age'); // age is optional
  schema.Date('created', {default: Date.now});
});

ハッシュとレンジキーを指定しての定義:

var BlogPost = vogels.define('Account', function (schema) {
  schema.String('email', {hashKey: true});
  schema.String('title', {rangeKey: true});
  schema.String('content');
  schema.StringSet('tags');
});

書き込み:

Account.create({email: 'foo@example.com', name: 'Foo Bar', age: 21}, function (err, acc) {
  console.log('created account in DynamoDB', acc.get('email'));
});

読み込み:

Account.get('test@example.com', function (err, acc) {
  console.log('got account', acc.get('email'));
});

簡単ですね!
クエリやセカンダリインデックスなどの使い方はドキュメントを参照してください。

入れ子構造の取り扱い方

ここからが本題です。
DynamoDBでは、MapとListという新しいデータ型を追加することで入れ子構造の格納を実現しています。
先述の通り、vogelsではスキーマを定義して、それに合わせたデータを出し入れします。
vogelsで入れ子構造を取り扱うには、このスキーマをMapとListを使用して定義します。

Map

MapはJavaScriptでいうオブジェクトです。まずは以下の例をみてください。

var Photo = vogels.define('Photo', function (schema) {
  schema.String('userid', {hashKey: true});
  schema.Map('location');
});

Photo.create({userid:'john', location: {name: "東京タワー", latitude:35.65858, longitude: 139.745433}}, console.log);

この例では、Photoモデルのlocation属性がMap型として定義されています。
中身には、場所に関するデータが格納されています。
単純ですね!
もちろん、このlocationの中身もスキーマで定義できます。

var Photo = vogels.define('Photo', function (schema) {
  schema.String('userid', {hashKey: true});
  schema.Map('location', function(schema) {
    schema.String('name');
    schema.String('latitude');
    schema.String('longitude');
  });
});

Mapの中に更にMapを格納することもできます
Mapの中身のスキーマを定義しなかった場合、未定義となり制約は与えられず、自由なフォーマットのデータが格納できます。

List

ListはJavaScriptでいう配列です。使い方は以下のとおり。

var Photo = vogels.define('Photo', function (schema) {
  schema.String('userid', {hashKey: true});
  schema.List('likes');
});

Photo.create({userid:'john', likes: ['emily']}, console.log);

こちらも直感的ですね。
Listの各要素の形式が同じで尚且つMap型の場合は、以下のように要素のスキーマを定義できます。

var Photo = vogels.define('Photo', function (schema) {
  schema.String('userid', {hashKey: true});
  schema.List('likes', function(schema) {
    schema.String('userid');
    schema.Date('likedAt');
  });
});

MongoDBとの併用はかなりイケてる?

JSON形式のドキュメントがそのまま格納できるDBMSとして、有名なのはMongoDBです。
今回のJSONサポートによって、DynamoDBはMongoDBと同じフォーマットでデータを取り扱えるようになりました。
これは、「併用しやすい」という事です。
併用すると何が嬉しいか、考えてみます。

DynamoDBは運用コストが低くスケーラビリティが高いのがウリのデータベースです。
その代わり、検索の柔軟性を犠牲にしています。
それに対してMongoDBは検索の柔軟性が高いのが強みです。
しかしながら、DaaSは価格が高いし自分で運用するのは大変です。
DynamoDBとMongoDBを併用することによって、互いの弱みを補い合えるかもしれません。

MongoDBを既に使っていた人にとっては、すごい良いニュースではないでしょうか。

iPhotoがアプリケーション切り替え時に「最後の読み込み」画面に強制遷移してしまう問題への対処法

iPhotoである問題に数年間苛まれていて、ずっと解決じまいでした。
それは、iPhotoを使っていて一旦Finderなど別のアプリに切り替えてからまたiPhotoへ切り替えた時、どの画面を表示していようと強制的に「最後の読み込み(英語だと”Last Import”)」画面に遷移してしまうというものです。

「最後の読み込み」画面とは、iPhoneやiPadから最後に取り込んだ写真の一覧を表示する画面です。

僕の場合、以下の手順で解消しました:

  1. iPhoto Library があるフォルダを開く(通常は ホーム > 写真)
  2. iPhoto Libraryを副クリック(Ctrlを押しながらクリック)して、ポップアップメニューを表示。メニューから「パッケージの内容を表示」を選択。
  3. “Auto Import”フォルダを探して開く
  4. そのフォルダの中身を全て削除する

フォルダの中身ですが、Finderだと隠しファイルが表示されません。
隠しファイルを削除するには、「ターミナル」を起動して以下のコマンドを打ち込みます。

$ cd "iPhoto Libraryへのパス"
$ ls -la "Auto Import/"
$ rm "Auto Import/.*"

これで無事直りました。よかったよかった!

参考

Locate your iPhoto Library ? in home > Pictures.
Bring up the contextual menu on iPhoto Library and select Show Package Contents.
Locate the “Auto Import” folder and open it.
Remove its contents ? which probably consist of empty folders or other stuff that can’t be imported.

via iPhoto automatically jumps to last import

iOSのクラッシュログファイルをシンボリケートする

どこでクラッシュしたのか見通しを良くする

Appleから受け取ったクラッシュログは、クラッシュした状態のスタックトレースをバイナリ上のアドレスで示されています。
このままだと、内容を見てもソースコード上のどこが悪いのかさっぱり分かりません。
シンボリケートとは、主にバイナリ上のアドレスからソースコードの場所を突き止める事をいいます。

使用するコマンド

クラッシュログのシンボリケートにはsymbolicatecrashコマンドを使用すると便利です。
XCode 6では以下のパスに存在しています。

/Applications/Xcode.app/Contents/SharedFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash

/usr/bin あたりにシンボリックリンクを貼っておくと吉。

必要なファイル

クラッシュログを吐いたバイナリと同じビルドのもので、以下が必要です:

  • appファイル
  • dSYMファイル

XCodeのOrganizerを開き、アーカイブ一覧から該当のビルドを探します。
次に、該当ビルドの項目を右クリックしてShow in Finderします。
開かれたディレクトリに、上記二つのファイルが存在します。

シンボリケートの実行

以下のように実行します。

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"

symbolicatecrash -v crashファイル dSYMファイル appファイル

トラブルシューティング

DEVELOPER_DIRを予め設定しておかないと、以下のようなエラーが出ます。

Error: "DEVELOPER_DIR" is not defined at /Applications/Xcode.app/[snip]Resources/symbolicatecrash line 53.

CoffeeScript(1.7+)で書いたテストをmochaで実行する

mocha

メモ。
検索しても古いやりかたしか出てこなかったので。

mochaはnode.js製のテストフレームワークです。
CoffeeScriptはJavaScriptのトランスパイラです。

CoffeeScriptで書いたテストをmochaで実行できるのですが ドキュメント によると、 CoffeeScript 1.7からそのコマンドが変わっています。

coffee-script is no longer supported out of the box. CS and similar transpilers may be used by mapping the file extensions (for use with –watch) and the module name. For example –compilers coffee:coffee-script with CoffeeScript 1.6- or –compilers coffee:coffee-script/register with CoffeeScript 1.7+.

というわけで、以下のように実行します:

$ mocha --compilers coffee:coffee-script/register

古いやり方で出るエラー

$ mocha --compilers coffee:coffee-script
/.../node_modules/coffee-script/lib/coffee-script/coffee-script.js:195
          throw new Error("Use CoffeeScript.register() or require the coffee-s
                ^
Error: Use CoffeeScript.register() or require the coffee-script/register module to require .coffee.md files.
  at Object._base.(anonymous function) [as .coffee] (/.../node_modules/coffee-script/lib/coffee-script/coffee-script.js:195:17)
  at Module.load (module.js:355:32)
  at Function.Module._load (module.js:310:12)
  at Module.require (module.js:365:17)
  at require (module.js:384:17)
  at /.../node_modules/mocha/lib/mocha.js:184:27
  at Array.forEach (native)
  at Mocha.loadFiles (/.../node_modules/mocha/lib/mocha.js:181:14)
  at Mocha.run (/.../node_modules/mocha/lib/mocha.js:393:31)
  at Object.<anonymous> (/.../node_modules/mocha/bin/_mocha:380:16)
  at Module._compile (module.js:460:26)
  at Object.Module._extensions..js (module.js:478:10)
  at Module.load (module.js:355:32)
  at Function.Module._load (module.js:310:12)
  at Function.Module.runMain (module.js:501:10)
  at startup (node.js:124:16)
  at node.js:842:3

koaをCoffeeScriptで書く

tl;dr

  • koaは、JavaScript(ES6)のgeneratorを活用したnode.js向けのwebフレームワークです
  • CoffeeScriptの開発版で、最近generatorの構文がサポートされました
  • 早速、試しにCoffeeScriptでkoaを使ってみたらちゃんと動きました

Generatorとは?

JavaScriptの機能の一つです。
JavaScriptでは非同期的な処理の結果はコールバック関数で受け取るのが通例です。
しかし、例えばネットワーク通信系の処理などでは、コールバックが多くなりすぎてコードがとても複雑になってしまう問題がありました。
Generatorは、このコールバック地獄を解決できると期待されている機能です。

以下に、node.jsによるコールバック関数を使った非同期処理を記します:

var fs = require('fs');

// カレント ディレクトリーのファイル一覧を取得する
fs.readdir('.', function(err, files) {
    // 先頭のファイルの中身を読み取る
    fs.readFile(files[0], 'utf-8', function(err, data) {
        // 読み取った結果を出力する
        console.log(data);
    });
});

みづらいですね。
しかしGeneratorを使うと:

var co = require('co');
var fs = require('fs');

co(function *() {
  var files = yield co.wrap(fs.readdir)('.');
  var data = yield co.wrap(fs.readFile)(files[0], 'utf-8');
  console.log(data);
});

シンプル!
構文的には、function*yieldキーワードが新しい要素です。

詳しくは下記の記事が分かりやすいので読んでみて下さい。

CoffeeScriptでgeneratorがサポートされた

ECMAScript 6でサポートされるgeneratorは、V8で実装されました。
V8を使っているnode.jsでも、unstableバージョンで使用出来ます。
先述の通り、このgeneratorは新しい構文が必要なため、CoffeeScriptでは使えませんでした。
しかし長い議論の末、開発版でついにgeneratorが対応になりました!finally!

上記Pull Requestによると、function内にyieldキーワードがあると、自動的にgenerator(function*)に変換されるようです。

さっそく使ってみる

node.jsはバージョン 0.11.9 以上を用意して下さい。

開発版のCoffeeScriptをインストールします:

$ npm install --save git://github.com/jashkenas/coffeescript.git

以下に簡単な例を示します(a2z.coffee):

a2z = ->
  c = 97

  while (c < = 'z'.charCodeAt(0))
    yield String.fromCharCode(c++)

g = a2z()
i = 0
console.log ++i, g.next().value
console.log ++i, g.next().value
console.log ++i, g.next().value

以下のコマンドで実行します:

$ coffee --nodejs --harmony a2z.coffee
1 'a'
2 'b'
3 'c'

使えました!

koaをCoffeeScriptで書く

koa は、node.js用の次世代Webフレームワークです。
generatorの仕組みを活用しています。
CoffeeScriptのgeneratorサポートによって、koaもCoffeeScriptで書けるようになりました。
さっそく書いてみます。

app.coffee:

koa = require 'koa'
app = module.exports = koa()

app.use (next)->
  this.body = yield (cb)->
    cb null, 'hello, world'

app.listen 3000 if !module.parent

以下のコマンドで実行します:

$ coffee --nodejs --harmony app.coffee

http://localhost:3000/ を開いてみましょう。
hello, worldと表示されたら成功です!

今後の進化が楽しみですね。