(2015年までの)odaillyjp blog

イベント参加記録とプログラミング系の雑記

-> { "Perfect Ruby Advent Calendar" }.call

この記事はパーフェクトRubyAdventCalendar2013の14日目の記事です。昨日の記事は、パーフェクトRuby Advent Calendar 2013(13日目) 気になるあの子でした。

感想

『パーフェクト Ruby』の制作に関わった方々、お疲れ様でした。Rake や Pry は適当に使っていたところがあったのですが、『パーフェクト Ruby』で便利な使い方を知ることができましたので、とても役に立ちました。

パーフェクトRuby (PERFECT SERIES 6)

パーフェクトRuby (PERFECT SERIES 6)

まずは、書籍の感想について書きます。パーフェクトシリーズは『パーフェクト JavaScript』と『パーフェクト PHP』を読んだことがあります。パーフェクトシリーズってタイトルだけを見ると、「そのプログラミング言語本体の仕様だけに専念した技術書」というイメージを持たれそうですが、そんなことはなく、そのプログラミング言語に関わる他の技術についても触れられています。例えば、『パーフェクト JavaScript』ならば HTML5 について、『パーフェクト PHP』ならばWebアプリケーションのセキュリティについても触れられています。Webアプリケーションのセキュリティの話なんて、他の言語を使っても絶対に必要になることだと思うのですが、あえて『パーフェクト PHP』で解説しているあたり、PHPのセキュリティに対する意識の高さに私は助けられました。
話が脱線しました。では、『パーフェクト Ruby』では Ruby 以外の技術で何について触れられているのでしょうか。技術評論社様の『パーフェクト Ruby』紹介ページの目次を見てみます。

パーフェクトRuby
http://gihyo.jp/book/2013/978-4-7741-5879-2?ard=1387013061

目次を見ると分かるのですが、『パーフェクト Ruby』では Ruby 本体や Ruby の有名なライブラリだけに絞って解説されており、他の技術についてはあまり触れられていません。「Rubyの話だけなのだから、他のパーフェクトシリーズと比べるとページ数が少ないのではないか」と思うかもしれませんが、そうでもありません。『パーフェクト Ruby』は『パーフェクト JavaScript』や『パーフェクト PHP』を超えるページ数で構成されています。これだけのページ数を使って Ruby だけの解説をしても、ページ数が足りなくて触れることができなかったネタがあるそうです。Ruby は恐ろしい…

Comparable モジュール

『パーフェクト Ruby』の164ページで紹介されている Comparable モジュールについて、まとめてみます。
Comparable モジュールは <=> メソッドを追加するだけで、一通りの比較演算子メソッドを提供してくれる便利なモジュールです。例えば、下記のコードのようにブログ記事を扱う BlogEntries クラスを定義します。

class BlogEntries
 attr_accessor :created_at, :body, :author

 def initialize
   yield self if block_given?
 end
end

この BlogEntries クラスのインスタンスオブジェクト同士を created_at の時間で比較できるようにしたいのですが、比較する方法を教えていませんので、このままでは比較できません。比較する方法を教える…つまり > や < や == といった比較演算子メソッドを自分で定義しなければいけません。面倒ですね。でも、Comparable モジュールを include すれば、 <=> メソッドを定義するだけで、いくつかの比較演算子メソッドが利用できるようになります。

require 'time'

class BlogEntries
 attr_accessor :created_at, :body, :author
 include Comparable

 def initialize
   yield self if block_given?
 end

 def <=>(other_entry)
   self.created_at <=> other_entry.created_at
 end
end

first_entry = BlogEntries.new do |entry|
 entry.created_at = Time.parse("2013-12-13")
 entry.body = "パーフェクトRuby"
 entry.author = "Shindo200"
end

second_entry = BlogEntries.new do |entry|
 entry.created_at = Time.parse("2013-12-14")
 entry.body = "AdventCalendar"
 entry.author = "Shindo200"
end

puts first_entry < second_entry  #=> true
puts first_entry > second_entry  #=> false
puts first_entry <= second_entry #=> true
puts first_entry >= second_entry #=> false
puts first_entry == second_entry #=> false
puts first_entry != second_entry #=> true

created_at の時間で比較できるようになっています。こんな便利なモジュールが最初から用意されているところが、 Ruby の良いところの一つだと思っています。

Observable モジュール

『パーフェクトRuby』では触れられていませんでしたが、使ってみて面白いと思った標準添付ライブラリである Observable モジュールについて紹介します。例えば、ブログ記事を保存したときに、TwitterFacebook に通知を送るような機能を実装するとします。

class Twitter
  def update(changed_entry)
    # TODO: Twitter に通知を送る処理
    puts "Twitter: #{changed_entry.author}がブログを更新しました。"
  end
end

class Facebook
  def update(changed_entry)
    # TODO: FaceBook に通知を送る処理
    puts "Facebook: #{changed_entry.author}がブログを更新しました。"
  end
end

class BlogEntries
  attr_accessor :created_at, :body, :author
  include Comparable

  def initialize
    @observers = []
    yield self if block_given?
  end

  def <=>(other_entry)
    self.created_at <=> other_entry.created_at
  end

  def add_observer(observer)
    @observers << observer
  end

  def save
    # TODO: ブログ記事を保存する処理
    @observers.each { |observer| observer.update(self) }
  end
end

entry = BlogEntries.new do |entry|
  entry.created_at = Time.now
  entry.body = "パーフェクトRuby"
  entry.author = "Shindo200"
  entry.add_observer(Twitter.new)
  entry.add_observer(Facebook.new)
end

entry.save

あらかじめインスタンス変数 observers に通知処理を持つクラスのオブジェクトを入れておき、保存処理(save メソッド)を呼び出したときに、インスタンス変数 observers に入ったオブジェクト全ての通知処理(update メソッド)を呼び出すようにしました。これでもコード量は多くはないのですが、Observable モジュールを使えば、もっと楽に書くことができます。

require 'observer'

class Twitter
  def update(changed_entry)
    # TODO: Twitter に通知を送る処理
    puts "Twitter: #{changed_entry.author}がブログ記事を更新しました。"
  end
end

class Facebook
  def update(changed_entry)
    # TODO: Facebook に通知を送る処理
    puts "FaceBook: #{changed_entry.author}がブログ記事を更新しました。"
  end
end

class BlogEntries
  attr_accessor :created_at, :body, :author
  include Comparable
  include Observable

  def initialize
    yield self if block_given?
  end

  def <=>(other_entry)
    self.created_at <=> other_entry.created_at
  end

  def save
    # TODO: ブログ記事を保存する処理
    changed
    notify_observers(self)
  end
end

entry = BlogEntries.new do |entry|
  entry.created_at = Time.now
  entry.body = "パーフェクトRuby"
  entry.author = "Shindo200"
  entry.add_observer(Twitter.new)
  entry.add_observer(Facebook.new)
end


entry.save

changed メソッドを呼び出して更新フラグを立ててから、notify_observers メソッドを呼び出すと、add_observer メソッドで登録したオブジェクト全ての update メソッドが呼び出されます。

module Observable
http://docs.ruby-lang.org/ja/2.0.0/class/Observable.html

以上、Observable モジュールのご紹介でした。Ruby は便利な標準添付ライブラリが多いですね。