initialize に渡ってきたパラメタをインスタンス変数に丸投げする
の続きです。ささださんに教えてもらいました。有名な奥の手、だそうです。
#!/usr/local/bin/ruby class Player attr :name, :age define_method(:initialize) { |@name, @age| } end player = Player.new('Rubyo', 19) player.name.display
たしかにこれで "Rubyo" と表示されました。define_method は Kernel モジュールに実装されているプライベートメソッドでしょうか。Kenerl.define_method
としたところ
class.rb:5: private method `define_method' called for Kernel:Module (NoMethodError)
と出ました。ri define_method では
Defines an instance method in the receiver. The _method_ parameter can be a +Proc+ or +Method+ object. If a block is specified, it is used as the method body. This block is evaluated using +instance_eval+, a point that is tricky to demonstrate because +define_method+ is private. (This is why we resort to the +send+ hack in this example.)
とありました。"ブロックが指定されるとそれはメソッド本体として扱われる。このブロックは instance_eval
によって eval される" のあたりがこのトリックの種明かしのようです。
ささださんによると 2.0 では使えなくなるかも、とのことです。ささださんありがとうございました。
RSS を YAML に変換する#2
の続きです。nuna さんがコメントで色々教えてくれました。
- バージョンが異なるフィードを、バージョンを意識せずに parse するには require を複数書くとよい (http://www.cozmixng.org/~rwiki/?cmd=view;name=RSS+Parser%3A%3ATutorial.ja にマニュアルあり。)
- YAML にするときにオブジェクトをそのまま渡すと、YAML で復元した側がそのオブジェクトのクラスを必要とするようになってしまう。これを回避するには RSS オブジェクトを Hash にすると良い。
- Hash にするには RSS::RDF に to_hash などを追加定義するとよい
とのことです。そこで、昨日のコードを書き替えてみました。
- require で rss/1.0 に加えて rss/2.0 も追加したので、RSS 2.0 を parse できるようになりました。
- to_hash メソッドの追加は RSS::Hashable というモジュールを使って Mixin でやってみました。RSS::RDF (1.0 用のクラス) と RSS::Rss に全く同じメソッドを追加する必要があったので。
#!/usr/local/bin/ruby require 'open-uri' require 'rss/1.0' require 'rss/2.0' require 'yaml' module RSS::Hashable def to_hash { :channel => { :title => self.channel.title, :link => self.channel.link, :description => self.channel.description }, :items => self.items.map do |item| { :title => item.title, :link => item.link, :description => item.description } end } end end class RSS::RDF include RSS::Hashable end class RSS::Rss include RSS::Hashable end class RSS2YAML def initialize(url) @url = url end def to_hash rss = self.parse_rss(@url) rss.to_hash end def to_yaml self.to_hash.to_yaml end def get_remote(url) begin return Kernel.open(@url).read rescue OpenURI::HTTPError return nil end end def parse_rss(url) content = self.get_remote(url) if content begin return RSS::Parser::parse(content) rescue RSS::InvalidRSSError return nil end end end public :to_yaml protected :get_remote, :parse_rss end if ARGV.size != 1 puts "Usage: ruby rss2yaml.rb <url>" exit end puts RSS2YAML.new(ARGV.shift).to_yaml
これで出力の yaml は ruby に依存しなくなりました。nuna さんありがとうございました。
RSS::Hashable の中身がちょっと Perl チックな書き方なのですが、こういうものでしょうか。Ruby は既存のクラスにメソッドを追加したりモジュールを Mixin したりするのが楽でいいですね。
ところで「たのしい Ruby」には普通は"モジュールの中では self は使いません" という記述があるのですが、Mixin 用モジュールでは使わないと書けないようなきもしますがどうなんでしょう。
Rails の API を調べる
Rails の API を調べるには id:secondlife さんによる http://api.rails2u.com/ を使うと便利そうです。
O/R マッパ(Active Record) を使ってみる
Ruby で O/R マッパを使って RDBMS を操作したいです。
Ruby on Rails に付属してくる O/R マッパの Active Record を使ってみます。Rails は元々インストールしていたので、そこから Active Record 単体で使ってみます。
mysql に、
+-------------+------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------------+------------------+------+-----+---------+----------------+ | id | int(10) unsigned | | PRI | NULL | auto_increment | | url | varchar(255) | YES | | NULL | | | title | varchar(255) | YES | | NULL | | | description | text | YES | | NULL | | +-------------+------------------+------+-----+---------+----------------+
というテーブルがあります。このテーブルは Active Record を使うつもりで設計したものなので、auto_increment な id カラムを持ち、テーブル名は "items" と複数形にしてあります。
#!/usr/local/bin/ruby require 'rubygems' require 'active_record' class Item < ActiveRecord::Base end ActiveRecord::Base.establish_connection( :adapter => 'mysql', :host => 'localhost', :username => 'rubyo', :password => '', :database => 'bookmark' )
とりあえずこれで、Active Record で bookmark データベースに接続して item テーブルを操作することができます。最初 require 'active_record'
とだけ書いていて、ライブラリが見つからなくて少しはまりました。rubygem を使ってインストールしたライブラリを使うには require 'rubygems'
も併記しておかないといけないようです。
手始めに、
#!/usr/local/bin/ruby require 'rubygems' require 'active_record' class Item < ActiveRecord::Base end ActiveRecord::Base.establish_connection( :adapter => 'mysql', :host => 'localhost', :username => 'rubyo', :password => '', :database => 'bookmark' ) Item.create( :url => 'http://d.hatena.ne.jp/rubyo/', :title => 'るびおの日記', :description => 'るびおのRuby勉強日記' ); Item.create( :url => 'http://d.hatena.ne.jp/rubyco/', :title => 'るびこの日記', :description => 'るびこのRuby勉強日記' ); Item.create( :url => 'http://b.hatena.ne.jp/rubyo/', :title => 'るびおのブックマーク', :description => 'るびおのRuby関連ブックマーク' );
として items に三つのレコードを追加しました。
次に items テーブルに検索系のクエリを投げてみました。
Item.find(:all, :limit => 2, :order => "id desc").each do |item| puts item.url end Item.find(:all, :conditions => "url like '%ruby%'").each do |item| puts item.url end puts Item.count puts Item.count(['title = ?', 'るびおの日記']) Item.find_all_by_title('るびおの日記').each do |item| puts item.url end
この結果は、
http://b.hatena.ne.jp/rubyo/ http://d.hatena.ne.jp/rubyco/ http://d.hatena.ne.jp/rubyo/ http://d.hatena.ne.jp/rubyco/ http://b.hatena.ne.jp/rubyo/ 3 1 http://d.hatena.ne.jp/rubyo/
となりました。Active Record はとても直感的で分かりやすいと思いました。Active Record について詳しくは Rails 本の P.207 から解説がありました。しっかり読んで理解したいと思います。
- 作者: 前田修吾
- 出版社/メーカー: オーム社
- 発売日: 2006/02/25
- メディア: 単行本(ソフトカバー)
- 購入: 2人 クリック: 109回
- この商品を含むブログ (207件) を見る
Enumerable で拡張してみます
ruby には Enumerable というモジュールがあり、集合を扱うクラス(コレクション)にこのモジュールを mix-in すると、Enumerable が備える各種メソッドが使えるようになるそうです。例えば
- collect
- find_all
- map
- grep
など各種メソッドが使えます。そこで、先の Iterator のコードの BookShelf を Enumerable を使って拡張してみたいと思います。
Enumerable を mix-in するにはウサギ本によるとコレクションで each メソッドを定義すれば良いようです。(Enumerable#max, #min, #sort を使うには <=> 演算子も定義する必要があります。) 先の BookShelf はすでに each メソッドを持っていますので、
class BookShelf include Enumerable def initialize @books = Array.new end def append_book(book) @books.push(book) end def each @books.each do |book| yield(book) end end end
と include Enumerable
を追加するだけで良かったです。これで Enumerable のメソッドが使えるようになりましたので、
(shelf.find_all do |book| book.name =~ /Daddy/i; end).each do |book| puts book.name end
こんなコードを実行することができました。
$ ruby iterator.rb Daddy-Long-Legs
- 作者: デビットトーマス,アンドリューハント,まつもとゆきひろ,David Thomas,Andrew Hunt,田和勝
- 出版社/メーカー: ピアソンエデュケーション
- 発売日: 2001/09/01
- メディア: 単行本
- 購入: 6人 クリック: 79回
- この商品を含むブログ (92件) を見る
Iterator パターン
id:hyuki さんのデザインパターン本の Iterator パターンを Ruby に移植してみます。
#!/usr/local/bin/ruby class Iterator def has_next end def next end end class Aggregate def iterator end end class Book attr_reader :name def initialize(name) @name = name end end class BookShelf < Aggregate def initialize @books = Array.new @last = 0 end def [](index) @books[index] end def append_book(book) @books[@last] = book @last += 1 end def length @last end def iterator BookShelfIterator.new(self) end end class BookShelfIterator < Iterator def initialize(bookshelf) @bookshelf = bookshelf @index = 0 end def has_next if (@index < @bookshelf.length) return true else return false end end def next book = @bookshelf[@index] @index += 1 return book end end shelf = BookShelf.new shelf.append_book(Book.new("Around the World in 80 Days")) shelf.append_book(Book.new("Bible")) shelf.append_book(Book.new("Cinderella")) shelf.append_book(Book.new("Daddy-Long-Legs")) it = shelf.iterator while (it.has_next) book = it.next puts book.name end
これを実行すると
$ ruby iterator.rb Around the World in 80 Days Bible Cinderella Daddy-Long-Legs
となります。
Java のコードをほぼそのままで getBookAt(int index)
を [](index)
にしてるところが違うぐらいのコードです。でもこれですと、
- Iterator クラス、Aggregate クラスはコードを実行する上では全く意味を為していない
- Java の場合は interface として書けるので意味がある
- while 文でイテレータを回すあたりが ruby らしくない
という感じがします。そこで http://www.freeml.com/message/patterns/997 で高橋さんが書いたコードのように each を使うようにしてみます。
#!/usr/local/bin/ruby class Book attr_reader :name def initialize(name) @name = name end end class BookShelf def initialize @books = Array.new end def append_book(book) @books.push(book) end def each @books.each do |book| yield(book) end end end shelf = BookShelf.new shelf.append_book(Book.new("Around the World in 80 Days")) shelf.append_book(Book.new("Bible")) shelf.append_book(Book.new("Cinderella")) shelf.append_book(Book.new("Daddy-Long-Legs")) shelf.each do |book| puts book.name end
shelf.each do |book| ... end
で回せるようになって ruby らしいコードになりました。BookShelfIterator として機能していた箇所は yield を使った each の中に内包されたので、クラスは二つだけになりました。
高橋さんのコードの最終形にもあるとおり、この場合の BookShelf は Array を継承するだけでも良かったりします。ただし、その場合 BookShelf がデータ構造を抽象化していることにはならないのかもしれません。
yield がすぐに頭に出てくるようになるにはもっと修行が必要そうです。
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2004/06/19
- メディア: 大型本
- 購入: 51人 クリック: 762回
- この商品を含むブログ (397件) を見る