12 Sep 2012

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

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

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

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

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

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

1つの解;
頂いた回答によると、インスタンスメソッドの中で変数にメソッドを追加すればいい。ここでいうと`decorate_it`
class HtmlSnippet < ActiveRecord::Base

  # getter is overrided to extend behaviour of freshly loaded values
  def content
    value = read_attribute(:content)
    decorate_it(value) unless value.respond_to?(:replace_url_to_anchor_tag)
    value
  end

  def content=(value)
    dup_value = value.dup
    decorate_it(dup_value)
    write_attribute(:content, dup_value)
  end

  private
  def decorate_it(value)
    class << value
      def replace_url_to_anchor_tag
        # ...
      end
    end
  end
end

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