今号のWEB+DB PRESS
- 作者: 鵜飼文敏,ミック,はまちや2,小飼弾,萩本順三,角田直行,きたみりゅうじ,小田慎一郎,河合太郎,大野道誉,宇野浩史,田中洋一郎,大塚知洋,nanto_vi,縣俊貴,岩澤直樹,下岡秀幸,山本陽平,伊藤直也,高井直人,永安悟史,角谷信太郎,高林哲,WEB+DB PRESS編集部
- 出版社/メーカー: 技術評論社
- 発売日: 2008/04/23
- メディア: 大型本
- 購入: 2人 クリック: 31回
- この商品を含むブログ (50件) を見る
「DSLヒッチハイク・ガイド」で使用したコード
Ruby勉強会@札幌-8で、「RubyでDSLを作成する際の作戦の立て方と表現のポイント」について発表した際に、使用したコードです。お題は「アンケート作成を支援するDSL」。
DSLじゃない実現
まず始めに、OOPで普通に実現したサンプル。ERBテンプレートはかなり適当なことになっていますが、スルーしてください。
#!/user/bin/ruby -Ku require 'erb' class Questionnaire TEMPLATE = <<EOP <b><%= context.title %></b><br/> <% context.questions.each do |q| %> <p><%= q.title %></p> <% q.answers.each do |a| %> <li><%= a %></li> <% end %> <% end %> EOP def self.create_html(context) puts ERB.new(TEMPLATE).result(binding) end end # ValueObject class Context attr_accessor :title, :questions def initialize title, questions @title = title @questions = questions end end class Question attr_accessor :title, :answers def initialize title, answers @title = title @answers = answers end end # Non-DSL sample ctx = Context.new("Ruby勉強会-8のアンケート",[]) ctx.questions << Question.new("勉強会への参加は初めてですか?", ["初めて", "常連"]) ctx.questions << Question.new("今回の勉強会はどうでしたか?", ["良かった", "まあまあ", "最低"]) ctx.questions << Question.new("次回も参加したいですか?", ["したい", "内容によって", "二度とくるか"]) Questionnaire.create_html(ctx)
グローバル・メソッドを使って
グローバル・メソッドを使用したDSLのサンプル。
#!/user/bin/ruby -Ku require "erb" # ValueObject class Context attr_accessor :title, :questions def initialize title, questions @title = title @questions = questions end end class Question attr_accessor :title, :answers def initialize title, answers @title = title @answers = answers end end TEMPLATE = <<EOP <b><%= @target.title %></b><br/> <% @target.questions.each do |q| %> <p><%= q.title %></p> <% q.answers.each do |a| %> <li><%= a %></li> <% end %> <% end %> EOP def context text @target = Context.new(text, []) yield puts ERB.new(TEMPLATE).result(binding) end def question text q = Question.new(text, []) yield q @target.questions << q end # DSL sample context "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do |q| q.answers << "初めて" q.answers << "常連" end question "今回の勉強会はどうでしたか?" do |q| q.answers << "良かった" q.answers << "まあまあ" q.answers << "最低" end question "次回も参加したいですか?" do |q| q.answers << "したい" q.answers << "内容によっては" q.answers << "二度と来ない" end end
コンテキスト部分の表現の改善
キーワード引数を導入し、コンテキスト部分の表現をちょっと改善。
... def context args @target = Context.new(args[:title], []) yield case args[:to] when :html puts ERB.new(TEMPLATE).result(binding) else p @target end end def question text ... end # DSL sample context :to => :html, :title => "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do |q| q.answers << "初めて" q.answers << "常連" end question "今回の勉強会はどうでしたか?" do |q| q.answers << "良かった" q.answers << "まあまあ" q.answers << "最低" end question "次回も参加したいですか?" do |q| q.answers << "したい" q.answers << "内容によっては" q.answers << "二度と来ない" end end
アンケート部分の表現の改善
つづいて、アンケート部分の表現の改善。だいぶアンケートっぽくなりました。
... def context args ... end def question text @target.questions << Question.new(text, yield) end # DSL sample with global functions (3) context :to => :html, :title => "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do ["初めて", "常連"] end question "今回の勉強会はどうでしたか?" do ["良かった", "まあまあ", "最低"] end question "次回も参加したいですか?" do ["したい", "内容によって", "二度とくるか"] end end
オブジェクトを使って
クラス・メソッドを使ったDSLのサンプル。
#!/user/bin/ruby -Ku require "erb" # ValueObject class Context attr_accessor :title, :questions def initialize title, questions @title = title @questions = questions end end class Question attr_accessor :title, :answers def initialize title, answers @title = title @answers = answers end end class Questionnaire TEMPLATE = <<EOP <b><%= @target.title %></b><br/> <% @target.questions.each do |q| %> <p><%= q.title %></p> <% q.answers.each do |a| %> <li><%= a %></li> <% end %> <% end %> EOP def self.context args @target = Context.new(args[:title], []) yield self case args[:to] when :html puts ERB.new(TEMPLATE).result(binding) else p @target end end def self.question text @target.questions << Question.new(text, yield) end end # DSL sample Questionnaire.context :to => :html, :title => "Ruby勉強会-8のアンケート" do |q| q.question "勉強会への参加は初めてですか?" do ["初めて", "常連"] end q.question "今回の勉強会はどうでしたか?" do ["良かった", "まあまあ", "最低"] end q.question "次回も参加したいですか?" do ["したい", "内容によって", "二度とくるか"] end end
メタプログラミングを使って
最後に、method_missingを利用したDSLのサンプル。
#!/user/bin/ruby -Ku require 'erb' TEMPLATE = <<EOP <b><%= @target.title %></b><br/> <% @target.questions.each do |q| %> <p><%= q.title %></p> <% q.answers.each do |a| %> <li><%= a %></li> <% end %> <% end %> EOP # Value Object class Context attr_accessor :title, :questions def initialize title, questions @title = title @questions = questions end end class Question attr_accessor :title, :answers def initialize title, answers @title = title @answers = answers end end def method_missing sym, *args case sym when :context @target = Context.new(args[0][:title], []) yield case args[0][:to] when :html puts ERB.new(TEMPLATE).result(binding) else p @target end when :question q = Question.new(args[0], yield) @target.questions << q end end #DSL sample context :to => :html, :title => "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do ["初めて", "常連"] end question "今回の勉強会はどうでしたか?" do ["良かった", "まあまあ", "最低"] end question "次回も参加したいですか?" do ["したい", "内容によって", "二度とくるか"] end end
Implementation Patterns
Implementation Patternsについてはこっちに書いてます。
公式HPオープン
From Sapporo, with Love for Ruby.Ruby札幌運営チームで公式HPを開設しました。
http://ruby-sapporo.org/
勉強会の動画の配信や資料置き場、その他情報伝達手段として、運用していこうと思います。
ご贔屓の程、どうぞよろしくお願いします。
また、運用の詳細についてはdaraさんのエントリに詳しいですので、そちらをご参照ください。
Query Method
オブジェクトのプロパティを調べるには、真偽値を返すメソッドを用意しましょう。メソッドには動詞または形容詞に'?'を付けた名前をつけましょう。
empty? nil? visible?
Rubyコーディング規約のメソッド名に関する規約も参照しましょう。
Debug Printing Method
Rubyでは、オブジェクトを表示可能な文字列に変換するための仕組みとしてinspectメソッドを用意しています。デバッグ用にオブジェクトの情報をプログラマに提供したい場合は、このinspectメソッドをオーバーライドしてデバッグ用の情報を生成しましょう。
collection = ["one", "two", "three"] p collection # => ["one", "two", "three"] def collection.inspect print "[first] " + self.first + ", " print "[last] " + self.last + ", " print "[size] " + self.size.to_s + "\n" end p collection # => [first] one, [last] three, [size] 3
Method Comment
メソッドに関するコメントを書く場合は、メソッドの中ではなくメソッドの先頭にコメントを書きましょう。また、コードから明白に読み取れない重要な情報のみ伝えるようにしましょう。
メソッド定義の中で書かれるコメントというのは、コードの補助的な記述です。Rubyのコードなら、補助的な記述抜きで大局的な情報をも伝えることが可能です。メソッドの定義の中にコメントを書く必要がありそうだと感じたら、その前にまず、変数名やメソッド名、引数名や処理の分割などで、その情報をコードで明示的に表現出来ないかを考えましょう。
また、もしコードから明白に読み取れない重要な情報が存在する場合は、メソッドの先頭にコメントするようにしましょう。それによって、コードの中にコメントを埋もれさせるよりは、読み手に情報が伝わる可能性を高くすることが出来ます。
Rubyコーディング規約のコメントに関する規約も参照しましょう。