config/routes.rbを分割する
とあるサービスのconfig/routes.rbが肥大化してきたので、分割したくなっていたので、調べていた。
ググってみると色々方法があるらしいが、バージョンごとにやり方が違ったりしてげんなりする。
よく出てくるconfig.paths["config/routes"]をいじる方法は、どうもRails4からは削除されていて動かないようだ。
ということで黒魔術を使った。めでたしめでたし。
# config/routes/some_module.rb module SomeModule def self.apply(context) context.instance_exec do # 実行させたい処理を書く # resources :products do # member do # get 'short' # post 'toggle' # end # end end end end
# config.rb Dir[Rails.root.join('config/routes/*.rb')].each do |file| load file end Your::Application.routes.draw do SomeModule.apply(self) end
解説
Your::Application.routes.drawの実態は、ActionDispatch::Routing::RouteSet#drawというやつのようで、その実装は、次のようになっている。
# actionpack/lib/action_dispatch/routing/route_set.rb def draw(&block) clear! unless @disable_clear_and_finalize eval_block(block) finalize! unless @disable_clear_and_finalize nil end
#eval_blockの実装は、
def eval_block(block) if block.arity == 1 raise "You are using the old router DSL which has been removed in Rails 3.1. " << "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/" end mapper = Mapper.new(self) if default_scope mapper.with_default_scope(default_scope, &block) else mapper.instance_exec(&block) end end private :eval_block
なので、drawしたときに作成する&blockの実態は、instance_execのコンテキストで実行されているようだ。
with_default_scopeするケースがあるが、こちらの実装は、
# actionpack/lib/action_dispatch/routing/mapper.rb def with_default_scope(scope, &block) scope(scope) do instance_exec(&block) end end
となっていて、こちらもinstance_execになっている。
なので、コンテキストを受けとって、コンテキストに対してinstance_execしてやれば同じコンテキストで実行できるので、instance_exec以降は通常のroutesと同じことが記述できる。
開発環境で再起動しなくても読みなおせるようにloadで読みこんでいるが、config/routes.rbのtimestampを更新しないと再読みこみしないのがどうしたもんかなーという感じではある。
続: CollectionViewで初回はemptyViewを表示しない
前のやつは、あまりよくない方法だなぁとは思っていたのですが、ちゃんとしたやり方っぽいのがあった。
"collection:rendered" triggerにフックすればよいようだ。Marionetteだと、triggerMethodというのがあって、triggerでも対応できるし、OnCamelCaseみたいなメソッドが生えていたらそれを自動で呼びだしてくれる。
var YourEmptyView = Marionette.ItemView.extend({ }); var YourCollectionView = Marionette.CollectionView.extend({ onCollectionRendered: function() { this.options.emptyView = YourEmptyView; } });
CompositeViewだと、onCompositeRenderedになる。
fadeInアニメーションさせたいときとかも、初回はアニメーションさせたくないけど、動的に追加したタイミングではアニメーションさせたいので、このトリガ内で、
var YourCollectionView = Marionette.CollectionView.extend({ onCollectionRendered: function() { this.OnBeforeItemAdded = function(view) { view.$el.fadeIn('fast'); }; } });
みたいにすると、良い感じ。
CollectionViewで初回はemptyViewを表示しない
あんまりよいやり方じゃないので、http://walf443.hatenablog.com/entry/2014/07/10/120223 を参照してください。
Marionette.jsのCollectionViewのemptyView機能は、collectionが0個になったら勝手に「データがありません」などの表示をしてくれて便利なのだけど、初回だけデータのロード待ちなどの関係で、emptyViewを表示したくない、ということが個人的には多いように思える。
そういうときは、showCollectionメソッドを上書きして、showCollectionが呼ばれたら初めて、options.emptyViewにemptyViewの設定が入るようにすればよい。
var YourEmptyView = Marionette.ItemView.extend({ }); var YourCollectionView = Marionette.CollectionView.extend({ showCollection: function() { this.options.emptyView = YourEmptyView; return Marionette.CollectionView.prototype.showCollection.apply(this, arguments); } });
一度でもコンテンツが追加されたら、showCollectionが呼ばれることになるので、それ以降にデータがなくなったりすると、emptyViewを表示してくれるようになる、というわけ。
Unicornでslow restart
Unicornの同時接続数がわりと少ないのに気づいたので、えいやとworker_processesを増やしたら今度はdeploy時にちょっと詰まり気味になってしまった。
これは、deployのタイミングで重いシステムコールであるforkを連発するため、と思われる。
そこで、Startletの--spawn-intervalのようなことをunicornでもやりたい。
設定ファイルで、
# config/unicorn.rb before_fork do # 起動時や再起動で一気にforkしまくると遅くなるので、intervalを加える if Unicorn::HttpServer::WORKERS.size > 10 sleep 0.4 end end
のようにやれば、起動時に連発してforkしなくなってよくなったっぽい。
もちろんbefore_forkは起動時のみに呼ばれるわけではなく、子プロセスの数が減って調整したタイミングや、unicorn-killerとかで殺されて再度起動した、みたいなケースでも呼ばれうるので、そういう場合にsleepが挟まっても問題ないか、は考慮する必要がありそう。
TCPListenerのファイルディスクリプタを調べる
ListenしているSocketのfdを知りたいが、net.Listenerのインターフェースをみても取得する方法がないのでググったら、
http://naoina.plog.la/2013/11/12/235753683181
こんな記事をみつけた。
Socketからfdが取得できないのは、POSIX縛りになるからだと思われるが、ここで方法へどうやって辿りついたかよくわからなかったので調べてみた。
TCPListenerの定義を調べると、
// src/pkg/net/tcpsock_posix.go type TCPListener struct { fd *netFD }
となっているので、reflectパッケージを使えば、
reflect.Indirect(reflect.Indirect(reflect.ValueOf(listener)).FieldByName("fd"))
で、fdの構造体が取得できる。
また、netFDの定義は、
// src/pkg/net/fd_unix.go // Network file descriptor. type netFD struct { // locking/lifetime of sysfd + serialize access to Read and Write methods fdmu fdMutex // immutable until Close sysfd int family int sotype int isConnected bool net string laddr Addr raddr Addr // wait server pd pollDesc }
となっているから、
uintptr(fdValue.FieldByName("sysfd").Int())
で、ファイルディスクリプタの値が取得できる、ということのようだ。
動いているVMにNatのポートフォワードの設定をCLIから変更する
Dockerをローカルで実行できるようになっても、ポート転送を手動で設定しないといけないのがつらいので、コマンドでやれるようにするのを調べた。
VBoxManage controlvm boot2docker-vm natpf1 "node,tcp,127.0.0.1,49160,,49160" VBoxManage controlvm boot2docker-vm natpf1 delete node
というようにすると、ポートフォワードの設定が変更できるので、ブラウザから立ちあげたコンテナへアクセスすることができます。
boot2docker-vmのマシンが起動していないときは、
VBoxManage modifyvm -boot2docker-vm natpf1 "node,tcp,127.0.0.1,49160,,49160" VBoxManage modifyvm -boot2docker-vm natpf1 delete node
boot2dockerへwrapperを書いてみたのだけど、既に色々このあたりを楽にしよう、というのは議論されているようなので、そのうち入るでしょう。
OSXでDockerを試す
雪がひどすぎて外出る気がしない。あー、そういえば、DockerがOSX対応したっていうし、ぼちぼち試してみるかー、と思って http://docs.docker.io/en/latest/installation/vagrant/ みてみたけど、あんまりかわってない気がする、というみなさんこんにちは。
そのページは前からあまり変わっていなくて、
https://github.com/dotcloud/docker/blob/master/docs/sources/installation/mac.rst
こっちが新しい手順らしい。
手順はそのままな気がするけど、homebrewでやればもうちょい簡単なようです。
まずは事前にVirtualBoxをInstallしておきます。
brew tap homebrew/binary brew install docker brew install boot2docker
とやればcurlでごにょごにょしなくてもPATHへ入れてくれます。
すると、boot2dockerとdockerというコマンドができるので、
boot2docker init boot2docker up
とやると、VirtualBox上でDockerが動作するLinuxマシンが起動する。
ローカルのOSXのポート転送してこのVM内のDocker daemonへ飛ばすように設定されているので、
OSX側のTerminalから、
DOCKER_HOST=localhost docker run ubuntu /bin/echo "hello docker"
とかやると、あたかもOSXからそのままDockerできるようになる、という感じらしい。
DOCKER_HOST=localhostをしないと、
2014/02/08 10:57:50 dial unix /var/run/docker.sock: no such file or directory
というエラーが出るのでexportしておくとよい。
あとは、ふつうにチュートリアルをひととおりこなせばよい。
最初と同じく更新が追いついてないっぽいので直接リポジトリ側をみた方がよさげ。
https://github.com/dotcloud/docker/tree/master/docs/sources/examples
Vagrantを入れてなくてもできるようになった。Vagrant sshしなくてもよい、というぐらいではありますが、確かにお手軽に試せるようになったようです。
追記
チュートリアルとかやるときにポートにバインドする例とかあるけど、残念ながら、OSX側にはバインドされないので、boot2docker sshしてログインしたあとにバインドしたポートを叩かないといけないもよう。このあたりvirtualboxのイメージに対して名前でアクセスできて、直接ポートアクセスできる、とかであればよいんだけどな。。。
VirtualBoxのマシンの設定をいじって、Host Onlyアダプタを追加しておけば、いちいちポートフォワードしなくてよいのでよさげ。