Magic Multi-Connectionsで複数のSQLite3ファイルを意識的に使う。

Magic Multi-Connectionsという分散DB対応ライブラリがあることをいまごろ知ったので簡単に試してみる。

とても分かりやすい記事があったので使わせていただきます。


想定として、Userモデル、UserInfoモデルがあり、

  1. Userはメインデータベースで管理する。
  2. UserInfoはユーザ別のSQLite3ファイルで管理する。

というケース*1

コネクションの切り替えは、試しにAR::Baseにメソッド [] を追加してみた。
これで

UserInfo[user.id].find :all

と、[]にコネクション識別値を渡して切り替えができる。


以下、サンプルコード。

require "rubygems"
require "fileutils"
require "active_record"
require "magic_multi_connections"

ActiveRecord::Base.logger = Logger.new(STDERR)
ActiveRecord::Base.colorize_logging = false

#メインDBを1個、ユーザ別DBを2個、用意する。
ActiveRecord::Base.configurations = {
  "maindb" => {:adapter => "sqlite3", :dbfile => "main.db"},
  "user_data_1" => {:adapter => "sqlite3", :dbfile => "UserInfo_1.db"},
  "user_data_2" => {:adapter => "sqlite3", :dbfile => "UserInfo_2.db"},
}
ActiveRecord::Base.configurations.each{|key, configuration|
  FileUtils.rm_f(configuration[:dbfile])
  ActiveRecord::Base.establish_connection(key)
  ActiveRecord::Schema.define {
    create_table("users"){|t|
      t.column "name", :string
    }
    create_table("user_infos"){|t|
      t.column "name", :string
    }
  }
}
#メインDBへの接続をデフォルトとする。
ActiveRecord::Base.establish_connection(:maindb)

#ARを改造。[]でコネクションを切り替えたクラスを返すようにしてみる。
class ActiveRecord::Base
  @@MMC_CONNECTION_HASH = {}
  @@MMC_AR_CLASS_HASH = {}
  def self.[](ident)
    unless (klass = @@MMC_AR_CLASS_HASH[ident])
      conn = nil
      dbfile = "#{self.name}_#{ident}.db"
      self.module_eval <<-MMC_MODULE
module MMC_CONNECTION_MODULE_#{ident}
  establish_connection(:adapter => "sqlite3", :dbfile => "#{dbfile}")
end
conn = MMC_CONNECTION_MODULE_#{ident}
klass = MMC_CONNECTION_MODULE_#{ident}::#{self.name}
MMC_MODULE
      @@MMC_CONNECTION_HASH[ident] = conn
      @@MMC_AR_CLASS_HASH[ident] = klass
    end
    return klass
  end
end

#ユーザクラス、ユーザ情報クラスを用意。
class User < ActiveRecord::Base
end
class UserInfo < ActiveRecord::Base
end

#ユーザ情報をメインDBに作成。
user_1 = User.create!(:name => "a")
user_2 = User.create!(:name => "b")

#ユーザ別情報を個別DBに作成
UserInfo[user_1.id].create!(:name => "a-1")
UserInfo[user_1.id].create!(:name => "a-2")
UserInfo[user_2.id].create!(:name => "b-1")
UserInfo[user_2.id].create!(:name => "b-2")

# データの確認
users = User.find(:all)
p users.collect(&:name) #=> ["a", "b"]
p UserInfo[users[0].id].find(:all).collect(&:name)  #=> ["a-1", "a-2"]
p UserInfo[users[1].id].find(:all).collect(&:name)  #=> ["b-1", "b-2"]


ちょっとしたコードで、いろいろなケースのマルチ接続に対応できるっぽ。
Rubyの変態レベルの高さを再認識。

*1:UserInfo用の個別DBファイルの作成・管理方法は、ここでは考慮しない