pumaに学ぶrubyのDSLの作り方

調べごとがあって、pumaのソースを読んだ。

そこでpumaの設定ファイルに書いてあるDSLの適用の処理が勉強になったので書く。

(puma: v2.11.0 時点)

pumaのソース

puma/dsl.rb at master · joe-re/puma · GitHub

ここでやっている。

主要な処理だけ抜き出した

主要な処理だけ抜き出して、少しだけ修正して分かりやすくした。

pumaに学ぶRubyのDSLの作り方

これを実行すると以下になる。

[~/src/sandbox/ruby_dsl]$ ruby dsl.rb
blockも渡せる
#<DSL:0x007fc39a1fc740 @options={:foo=>"test", :boolean_value=>true}>

config.rbで設定した値が適用されたのが分かる。

いいところ

configファイルを間違って記述したときに、分かりやすい形でエラーを返してくれる。

config.rbを書き換える。

foo 'test'

block_test do
  puts 'blockも渡せる'
end

# コメントは無視される

# boolean_test
boolean_toast # typo!

実行する

[~/src/sandbox/ruby_dsl]$ ruby dsl.rb
blockも渡せる
config.rb:10:in `_load_from': undefined local variable or method `boolean_toast' for #<DSL:0x007f91c106c570> (NameError)
...

config.rbの10行目のboolean_toastが違うよ!と言ってくれる。賢い。

Rubyで書ける

DSLとは言え、コメントとかブロック書くときにRubyのルールで書けるのはうれしい。

実装が簡単

分かりやすいメタプログラミング

解説

DSLクラスの_load_formメソッド内のinstance_evalに注目する。

instance_evalはrubyのBasicObjectに定義されているので、全てのオブジェクトで使えるインスタンスメソッドだ。

evalと同様、文字列が渡されたときはそれをRubyのコードとして評価するのだけど、違うのはinstance_evalのレシーバが評価のコンテキストになるということ。

この場合、レシーバを書かずにインスタンスメソッド内で実行しているので、レシーバはselfになる。

下と同じ。

self.instance_eval(File.read(path), path) if path

第1引数には、File.read(path)でファイルを読み込んだ文字列が渡されている。DSLクラスのインスタンスをコンテキストにして、この文字列が評価される。よって、config.rbに書いたDSLと同名のDSLクラスのインスタンスメソッドが呼ばれる。

instance_evalメソッドの第2引数にはファイルパスを渡していて、これのおかげでエラーが分かりやすくなる。

渡したファイルパスで実行されているようにスタックトレースを差し替えてくれるのだ。

instance method BasicObject#instance_eval

終わりに

unicornも読んでみたらほぼ同じ仕組みだった。

デザインパターンなのか?今日学んだパターンの名前を僕はまだ知らない。

ヘッドホン買った

JBLのS400BTという機種。 f:id:joe-re:20150317065518j:plain

JBLには思い出が少しある。学生時代に入ってた軽音楽サークルのスピーカーがJBLだった。

僕はPAというセクションで学内ライブとかの音響を担当していたので、スピーカーケーブル作ったりしてた。懐かしい。

一番の購買動機はbluetooth対応の機種が欲しかったというところ。

今まで有線のイヤホン使ってた。コーディングに夢中になってくるといつのまにかケーブルがキーボードに浸食してきてたりするのが非常にうざかった。(iMacだとヘッドホン後ろに刺すので特に。)

通勤の時も線がやっぱり邪魔。なくせるなら線はない方がいい。

一応バンドマンなので音にもこだわりみたいなのはあって、やたらと重低音推しなものは買わないようにしている。

1万円付近かそれ未満の安いものには、重低音推しなものが多くて、それは重低音強めにすればなんとなく良い音に聞こえるからだ。(と勝手に思っている。)

それはそれで間違ってないような気もしていて、確かにお店で聴く限りでは満足できる。

けどずっと使っていると、一音一音がはっきり分解して聞こえないのが気になってきてしまう。このギターもっと際立ってるはずなのに!!とか感じてしまう。

重低音推しでも高いものであればまた違うのかも知れないけど、僕は買ったことないので分からない。

あんまり高いものも買えないので、2万円代ぐらいのものを沢山試したのだけど、この機種が一番音がクリアに聞こえた。

こういうヘロヘロな音楽が好きなのも、重低音軽視の原因になってる可能性はある。 www.youtube.com

やっぱりbluetoothだと音は若干劣化する感はあって、有線とくらべて音の深さみたいなものが消える。具体的には、多分低音がなくなってる。

それでも線ないのはやっぱりいい。結構bluetoothもしっかり繋がってて、家でタバコ吸うときはPCから5mぐらい離れた台所に行くんだけど、それぐらいなら余裕で維持するのでストレスレス。

つらいところ

iPhoneiMacとで、ペアリングの切り替えが若干面倒(ボタン一つでやりたい)

締め付け軽めを選んだつもりだったけど、それでもメガネと併用すると痛い(慣れでどうにかしたい)

yeoman-generatorでRailsアプリケーションのためのAngularJSのGeneratorを作りかけた

途中で飽きてしまった。

joe-re/generator-angular-rails · GitHub

Railsは一切のHTMLを返さないAPIサーバにして、フロントのソースコードRailsのディレクトリに一切置かない、みたいな構成が僕は好きだ。

リポジトリを完全に分けてもいいんだけど、初期段階では2つリポジトリがあるのが大仰だし、Rails書くのとフロント書くのも同時にやることが多いので、あまり違うリポジトリであることを意識したくない。

それならgulpfileをRailsのアプリケーションルートに置いて、railsコマンド叩くのと同じ感覚でフロントのビルドやらテストやら叩ければいいなー、と思ってて、そういうgeneratorが見当たらなかったから作ってみようとした。

これを使うとRailsのルートディレクトリにgulpfileやらpackage.jsonやら、わらわらできる。

ngapp、ngtestというディレクトリも作られて、これがそれぞれAngularのアプリケーション用のディレクトリとunitテストのディレクトリになる。

gulp buildすると、もともとあるRailsのPublicディレクトリは消しとばして、Agnularのアプリケーションに置き換わる。

generator-angulardependencyに入れていて、generatorのサブコマンドとして使える。(つまりこいつは最初のセットアップしか仕事しない。)

gulpあんまり触ったことなかったので使ってみたり、yeoman-generatorの仕組み知れたりして結構楽しかった。

けど、他の優秀なgenerator読んでると、対応してるオプションとか至れり尽くせりですげーな、今はここまでやる気力ないなー、って思ってしまったりして、ちょっとモチベーションがなくなってしまった。そのうちやる気取り戻して完成させたい。

とにかくこういう構成にしたい!みたいな気持ちぐらいはなんとか形になった気がする。

npmとして公開もしてないけど、自前でシンボリックリンク貼ったりすれば一応使えます。

もし興味がある方は試してみてください。

gulpとかyeoman-generatorとかまだまだちゃんと書けてないと思いますので、ご指導ください。