12 Sept 2012

Rubyでインスタンスの属性にクラス内からメソッドを追加する

Stackoverflowで`Rubyでインスタンスの属性にメソッドを追加する`方法を聞いたら、ボヘミアの田舎町から解答が付いた

題名が何いってるかわからんかもしれないのでどういうことか説明します。
ちょいちょいActiveRecord::Baseのattributeだけにメソッドを足してこういう風に書きたいことがあった。
  1. html_snippet = HtmlSnippet.find(1)  
  2. html_snippet.content = "Link to http://stackoverflow.com"  
  3. html_snippet.content.replace_url_to_anchor_tag!  
  4. # => "Link to <a href="http://stackoverflow.com">http://stackoverflow.com</a>"  

これを実現するのにそのクラス定義内にcontentにreplace_url_to_anchor_tag!を追加するメソッドを定義したかった。

ActiveRecordのクラスはこんなかんじ
  1. # app/models/html_snippet.rb  
  2. class HtmlSnippet < ActiveRecord::Base      
  3.   # I expected this bit to do what I want but not  
  4.   class << @content  
  5.     def replace_url_to_anchor_tag!  
  6.       matching = self.match(/(https?:\/\/[\S]+)/)  
  7.       "<a href="#{matching[0]}">#{matching[0]}</a>"  
  8.     end  
  9.   end  
  10. end  

↑あきらかにClass classの@content(常にnil)にメソッドを定義してるため動かない。

1つの解;
頂いた回答によると、インスタンスメソッドの中で変数にメソッドを追加すればいい。ここでいうと`decorate_it`
  1. class HtmlSnippet < ActiveRecord::Base  
  2.   
  3.   # getter is overrided to extend behaviour of freshly loaded values  
  4.   def content  
  5.     value = read_attribute(:content)  
  6.     decorate_it(value) unless value.respond_to?(:replace_url_to_anchor_tag)  
  7.     value  
  8.   end  
  9.   
  10.   def content=(value)  
  11.     dup_value = value.dup  
  12.     decorate_it(dup_value)  
  13.     write_attribute(:content, dup_value)  
  14.   end  
  15.   
  16.   private  
  17.   def decorate_it(value)  
  18.     class << value  
  19.       def replace_url_to_anchor_tag  
  20.         # ...  
  21.       end  
  22.     end  
  23.   end  
  24. end  

なんかやりたいことに対してコーディング量がtoo muchじゃないかなとか思うんだけど、万が一もっと簡潔な方法があったら教えてください。