今号のWEB+DB PRESS

WEB+DB PRESS Vol.44

WEB+DB PRESS Vol.44

伊藤直也さんの「Recent Perl World」に、パターン関連の記事の引用先として、本はてダを取り上げていただいていました。うれしすぐる。ということで、WEB+DB PRESS Vol.44を手にされた方は、是非読んでみて下さいませ。

「DSLヒッチハイク・ガイド」で使用したコード

Ruby勉強会@札幌-8で、「RubyDSLを作成する際の作戦の立て方と表現のポイント」について発表した際に、使用したコードです。お題は「アンケート作成を支援する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

公式HPオープン

From Sapporo, with Love for Ruby.
Ruby札幌運営チームで公式HPを開設しました。
http://ruby-sapporo.org/


勉強会の動画の配信や資料置き場、その他情報伝達手段として、運用していこうと思います。
ご贔屓の程、どうぞよろしくお願いします。


また、運用の詳細についてはdaraさんのエントリに詳しいですので、そちらをご参照ください。

Query Method

オブジェクトのプロパティを調べるには、真偽値を返すメソッドを用意しましょう。メソッドには動詞または形容詞に'?'を付けた名前をつけましょう。

empty?
nil?
visible?

Rubyコーディング規約のメソッド名に関する規約も参照しましょう。

Debug Printing Method

デバッグ用にオブジェクトの情報をプログラマに提供するためには、inspectメソッドをオーバーライドしましょう。

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コーディング規約のコメントに関する規約も参照しましょう。