AngularJSのdeferredで並列実行
jQueryの$.whenみたいに複数のdeferredを同時に処理するみたいのはAngularJSだと$q.allでできる。
var d1 = $q.defer(); var d2 = $q.defer(); var d3 = $q.defer(); $timeout(function() { console.log('d1'); d1.resolve('d1'); }, 10); $timeout(function() { console.log('d2'); d2.resolve('d2'); }, 1000); $timeout(function() { console.log('d3'); d3.resolve('d3'); }, 200); $q.all([ d1.promise, d2.promise, d3.promise ]).then(function(result) { console.log(result); //=> ['d1', 'd2', 'd3'] });
引数は配列でもオブジェクトでもいける。
$q.whenというのもあるけどこれはオブジェクトを$qのdeferredにラップするみたいな感じっぽいので並列処理ができるやつではない。
AngularJSのControllerをネストしたときのスコープ
<div ng-controller="ParentCtrl"> <div ng-show="isShow">foo</div> <div ng-controller="ChildCtrl"> <button ng-click="toggle()">click</button> </div> </div>
こういうHTMLがあったときに、ChildCtrlから$scope.isShowを操作しても反映されない。
これだとダメ。
var app = angular.module('app', []); app.controller('ParentCtrl', function($scope) { $scope.isShow = false; }); app.controller('ChildCtrl', function($scope) { $scope.toggle = function() { $scope.isShow = !$scope.isShow; }; });
$scope.$parent.isShowの値を更新ればいける。
app.controller('ParentCtrl', function($scope) { $scope.isShow = false; }); app.controller('ChildCtrl', function($scope) { $scope.toggle = function() { $scope.$parent.isShow = !$scope.isShow; }; });
どうやら
$scope.__proto__ === $scope.$parent
となっているようで、文字通り親のコントローラーを継承しているようになってるみたい。
なのでChildCtrlで$scope.isShowの値を参照しようとすると親のコントローラーの値を参照できるんだけど、代入すると自分自身のプロパティとして新しくつくるんで親には影響しない、ということかな。
なので、以下のように更新する値をオブジェクトにするといける。
var app = angular.module('app', []); app.controller('ParentCtrl', function($scope) { $scope.data = { isShow: false }; }); app.controller('ChildCtrl', function($scope) { $scope.toggle = function() { $scope.data.isShow = !$scope.data.isShow; }; });
これは$scope.dataを新しくつくるのでなく$scope.__proto__.dataを更新するからだね。
AngularJSでimgのsrcにバインドするときに404になる
<img src="{{image}}">
app.controller('MainCtrl', function($scope) { $scope.image = 'path/to/image'; });
とかする場合、読み込み時に {{image}} にリクエストが飛んで404になる。
ng-srcを使うといいらしい。
<img ng-src="{{image}}">
hub pull-requestでブランチを指定する
これ見て試してみたら、devブランチに対してpull requestしたいのにmasterブランチに対してしてしまって悲惨なことになった。(そしてこの操作は戻せないという・・)
http://qiita.com/kyanny/items/170a188a87925f81ae86
デフォルトはmasterだから -b オプションで指定してねってことらしい。
https://github.com/github/hub/issues/154
$ hub pull-request -i 100 -b dev
でいけた。
Mongooseのバージョニング
Mongooseは3からバージョニングという機能が入ったんだけどよくわかってなかったので調べたメモ。
詳しくはここに書いてある。
http://aaronheckmann.tumblr.com/post/48943525537/mongoose-v3-part-1-versioning
バージョニングを使うと何がいいかというと、配列でネストしたスキーマをもつ場合などに、更新や削除の処理が並列に走ると要素がずれるという問題が解決できるらしい。
次のような検証スクリプトを書いた。
var mongoose = require('mongoose') , Schema = mongoose.Schema , ObjectId = mongoose.Schema.ObjectId; mongoose.connect('mongodb://localhost/version_key_test', function(err) { console.log(err); }); mongoose.set('debug', true); var commentSchema = new Schema({ body: String , user: String , created: { type: Date, default: Date.now } }); var postSchema = new Schema({ comments: [commentSchema] }); var Post = mongoose.model('Post', postSchema); Post.remove(function() { var post = new Post(); post.comments.push({ body: '1', user: '1'}); post.comments.push({ body: '2', user: '2' }); post.comments.push({ body: '3', user: '3' }); var commentId = post.comments[1]._id; post.save(function(err, post) { // 最初の要素を削除 Post.findById(post._id, function(err, post) { post.comments.shift(); post.save(function(err) { if (err) console.log(err); }); }); // 二番目の要素を更新 Post.findById(post._id, function(err, post) { setTimeout(function() { var comment = post.comments.id(commentId); comment.body = 'new 2'; post.save(function(err) { if (err) console.log(err); }); }, 100); }); }); });
これはコメントの削除と更新が同時に行われたケースを再現してる。これを実行すると、Mongoose v2.xでは意図せぬ挙動になる。具体的には次のような結果になる。
> db.posts.find().pretty() { "_id" : ObjectId("5215ddc11c18b90000000002"), "comments" : [ { "body" : "2", "user" : "2", "_id" : ObjectId("5215ddc11c18b90000000004"), "created" : ISODate("2013-08-22T09:45:37.903Z") }, { "body" : "new 2", "user" : "3", "_id" : ObjectId("5215ddc11c18b90000000005"), "created" : ISODate("2013-08-22T09:45:37.903Z") } ] }
user 3 のcomments.body が new 2 になってるのがわかる。なぜかというと、更新のほうで次のようなクエリが走るから。
// 更新のクエリ posts.update( { _id: 5215ddc11c18b90000000002 }, { '$set': { 'comments.1.body': 'new 2' } } })
comments.1.body は findById で post を取得した時点では user 2 のデータを指してるんだけど、post を取得してから更新の処理が走る間に削除の処理が走るために user 3 のデータが書き換わってしまうというわけ。
これが Mongoose v3.x のバージョニングを使えば解決できる。v3.x では __v というフィールドを Mongoose が勝手に作って、配列の要素を削除や追加した場合に __v の値をインクリメントする。そして更新の際に __v の値を find の条件に入れるので、上記のスクリプトを実行すると、更新の処理がエラーになる。クエリはこんな感じで走る。
// 削除のクエリ posts.update( { _id: ObjectId("5215ddde10f4fd0000000002"), __v: 0 }, { '$inc': { __v: 1 }, '$set': { comments: ... } } ) // 更新のクエリ posts.update( { _id: ObjectId("5215ddde10f4fd0000000002"), __v: 0 }, { '$set': { 'comments.1.body': 'new 2' } } )
更新のクエリはさっきと同じように comments.1.body を変更しようとしてるんだけど、検索部分に __v: 0 というのが追加されている。そしてその前の削除のクエリで '$inc': { __v: 1 } という処理を実行しているため、更新のクエリは条件にマッチせず、更新自体が行われない。
更新のほうのsaveメソッドでは次のようなエラーが発生する。
{ message: 'No matching document found.', name: 'VersionError' }
__v とかいうフィールドを持たせるのかなりダサイけどまあ現実的な解決方法なんだろうな、たぶん。
しかしトランザクションがないとこういう処理が大変なんだんなあ・・
要素の属性を全部取得する
例えば
<div data-foo="a" data-bar="b" data-baz="c">...</div>
みたいに任意のカスタムデータ属性がついてて、こっから
{ foo: 'a', bar: 'b', baz: 'c' }
みたいなデータを作りたいので属性を全部取得したいんだけど、 jQuery ではできないっぽい。
DOMの element.attributes を使えばいける。
var div = document.querySelector('div'); var attrs = div.attributes; for (var i = 0, len = attrs.length; i < len; i++) { console.log(attrs[i].name); //=> data-foo, data-bar, data-baz console.log(attrs[i].value); //=> a, b, c }
ほぼ使いどころないけど jQuery プラグインで書くんだったらこんな感じだろうか
$.fn.attrAll = function() { var ret = {}; var attrs = this.get(0).attributes; var attr; for (var i = 0, len = attrs.length; i < len; i++) { attr = attrs[i]; ret[attr.name] = attr.value; } return ret; };
console.log( $('div').attrAll() ); // { data-foo: 'a', data-bar: 'b', data-baz: 'c' }
追記: data() でできた