There's an echo in my head

日々のメモ。

ActiveRecordのserializeはYAML以外の読み書きにも使える

serialize-rails gemでJSONをDBに格納するという記事を書いたけど、わざわざgemを使わなくても第2引数に渡すオブジェクトがloaddumpを実装していれば十分だった。ちなみにActiveRecord 3.2で確認。

たとえばURLをURIのオブジェクトとして扱いたい場合。

# app/models/uri_coder.rb
class UriCoder
  # ActiveRecord::Baseのインスタンスが作られるときに呼ばれる。
  # DBから読み出したときにはstringには保存されている値が渡され、
  # newされたときにはnilが渡される。
  def load(string)
    URI.parse(string) if string.present?
  end
  # DBに書き込むときに呼ばれる。
  def dump(uri)
    uri.to_s
  end
end

# app/models/page.rb
class Page < ActiveRecord::Base
  # 第2引数に渡されたオブジェクトのload/dumpが使われる。
  serialize :url, UriCoder.new
end

実行してみるとこんな感じ:

page = Page.new
page.url = URI.parse("http://apple.com")
page.save!
#=> INSERT INTO "pages" ("url") VALUES ("http://apple.com")
# DBに保存するタイミングでdumpが呼ばれてStringに変換される

page_id = page.id

page = Page.find(page_id)
p page.url
#=> #<URI::HTTP:0x007fedc8bf4bf0 URL:http://apple.com>
# DBから読みだしたタイミングでloadが呼ばれてURIに変換される

以上、下記のTweetを見てserialize使えないかなーと思って試してみた次第。

ただ上の例だと代入するときにURIのオブジェクトに事前に変換していてちょっと面倒。直でStringを代入するとちょっと微妙…:

page = Page.new
page.url = "http://apple.com"
p page.url
#=> "http://apple.com"
# 代入のタイミングではloadが走らないので文字列のまま。ちょっと不便。

page.save!
#=> INSERT INTO "pages" ("url") VALUES ("http://apple.com")
# 保存は前述の場合と変わらずURLの文字列がそのまま保存される

p page.url
#=> #<URI::HTTP:0x007fedcb122df0 URL:http://apple.com>
# 保存したタイミングでloadが走るのか、URIに変換されてる。

ここはやっぱりread_attributewrite_attributeをオーバーライドするのが良さそう。この方法ならupdate_attributesにも使えるし。

参考

このブログに出てくるコードスニペッツは、引用あるいは断りがない限りMITライセンスです。